From 7c4ffdcc375b70d668b50197663e9733be3fe788 Mon Sep 17 00:00:00 2001 From: Marius Date: Tue, 29 Jun 2021 22:42:59 +0200 Subject: [PATCH] Initial commit --- .gitignore | 2 + LICENSE.txt | 21 + README.md | 38 + auditorium/auditconfig/acutil/syntax.go | 57 ++ auditorium/auditconfig/acutil/util.go | 357 ++++++++ .../interpreter/audit_interpreter.go | 349 +++++++ .../interpreter/audit_validator.go | 67 ++ auditorium/auditconfig/parser/parser.go | 421 +++++++++ .../syntaxchecker/syntaxchecker.go | 285 ++++++ .../config-interpreter/config_interpreter.go | 171 ++++ .../config/config-parser/config_parser.go | 438 +++++++++ .../config-parser/config_parser_flags.go | 291 ++++++ .../modulecontroller/module_controller.go | 347 +++++++ auditorium/modulecontroller/os_detector.go | 63 ++ .../outputgenerator/output_generator.go | 255 ++++++ go.mod | 11 + go.sum | 52 ++ icon.ico | Bin 0 -> 43821 bytes main.go | 245 +++++ models/cli.go | 36 + models/dot_jba.go | 57 ++ models/module.go | 37 + modules/0_methodhandler.go | 5 + modules/0_template.go | 33 + modules/auditctl.go | 60 ++ modules/authselect.go | 61 ++ modules/awk_script.go | 56 ++ modules/bash_script.go | 46 + modules/check_partition.go | 78 ++ modules/execute_command.go | 67 ++ modules/file_content.go | 98 ++ modules/grep.go | 69 ++ modules/is_file.go | 42 + modules/is_installed.go | 42 + modules/is_not_installed.go | 41 + modules/modprobe.go | 71 ++ modules/nft_list_ruleset.go | 74 ++ modules/permissions.go | 38 + modules/script.go | 174 ++++ modules/sshd.go | 59 ++ modules/stat.go | 54 ++ modules/sysctl.go | 46 + modules/systemctl.go | 43 + modules/win_audit_policy_query.go | 68 ++ modules/win_dump_security_settings.go | 68 ++ modules/win_export_installed_software.go | 128 +++ modules/win_get_account_name.go | 85 ++ modules/win_get_user_sid.go | 52 ++ modules/win_get_win_env.go | 51 ++ modules/win_is_gp_template_present.go | 83 ++ modules/win_registry_query.go | 55 ++ modules/win_security_settings_query.go | 66 ++ static/static.go | 178 ++++ .../audit_interpreter_test.go | 128 +++ test/auditconfig_test/audit_parser_test.go | 852 ++++++++++++++++++ test/auditconfig_test/audit_validator_test.go | 63 ++ test/config_test/cli_auditcfg_test.go | 215 +++++ test/config_test/cli_basic_param_test.go | 363 ++++++++ .../cli_configini_commandline_test.go | 51 ++ test/config_test/cli_configini_test.go | 99 ++ test/config_test/cli_test.go | 168 ++++ test/modules_test/script_test.go | 69 ++ .../output_generator_test.go | 101 +++ test/testdata/cli_testdata/Test1/audit.jba | 0 .../cli_testdata/Test1/audit_in_folder.jba | 0 .../Test1/folder_with_audit/cli_audit.jba | 0 test/testdata/cli_testdata/Test10/config.ini | 2 + test/testdata/cli_testdata/Test11/config.ini | 2 + test/testdata/cli_testdata/Test12/audit.txt | 1 + test/testdata/cli_testdata/Test12/config.ini | 2 + test/testdata/cli_testdata/Test2/audit.jba | 0 .../cli_testdata/Test2/audit_in_folder.jba | 0 test/testdata/cli_testdata/Test2/config.ini | 2 + .../Test2/folder_with_audit/config_audit.jba | 0 .../cli_testdata/Test3/testauditxy.jba | 5 + test/testdata/cli_testdata/Test4/audit.jba | 0 .../cli_testdata/Test4/audit_in_folder.jba | 0 .../cli_testdata/Test5/audit_in_folder1.jba | 0 .../cli_testdata/Test5/audit_in_folder2.jba | 0 test/testdata/cli_testdata/Test7/audit1.jba | 0 test/testdata/cli_testdata/Test7/audit2.jba | 0 test/testdata/cli_testdata/Test7/config.ini | 2 + .../folder_with_audit/audit_in_folder.jba | 0 .../testdata/cli_testdata/Test8/testaudit.jba | 0 test/testdata/cli_testdata/Test9/config.ini | 2 + test/testdata/cli_testdata/auditDummy.jba | 0 test/testdata/cli_testdata/auditDummy2.jba | 0 test/testdata/cli_testdata/basic_config.ini | 5 + test/testdata/cli_testdata/empty_lines.ini | 5 + test/testdata/cli_testdata/missing_key.ini | 5 + test/testdata/cli_testdata/missing_values.ini | 5 + .../missing_values_except_loglevel.ini | 5 + test/testdata/cli_testdata/mixed_order.ini | 5 + .../cli_testdata/negative_loglevel.ini | 5 + .../cli_testdata/overwrite_audconf.ini | 2 + .../cli_testdata/overwrite_booleans.ini | 7 + .../cli_testdata/overwrite_output.ini | 2 + .../cli_testdata/overwrite_verbosity.ini | 3 + .../cli_testdata/spaces_and_quotation.ini | 5 + test/testdata/cli_testdata/static.ini | 4 + test/testdata/cli_testdata/static_local.ini | 4 + test/testdata/filme.txt | 12 + .../parser_testdata/additional_alias.jba | 8 + .../parser_testdata/additional_comma.jba | 6 + test/testdata/parser_testdata/backticks.jba | 7 + .../testdata/parser_testdata/basic_module.jba | 6 + .../parser_testdata/basic_module_nested.jba | 12 + .../parser_testdata/brace_in_param.jba | 7 + .../parser_testdata/brace_in_value.jba | 6 + .../parser_testdata/case_sensitivity1.jba | 14 + .../parser_testdata/case_sensitivity2.jba | 14 + .../case_sensitivity_script1.jba | 9 + .../case_sensitivity_script2.jba | 9 + .../parser_testdata/colon_in_value.jba | 6 + test/testdata/parser_testdata/colon_only.jba | 6 + .../parser_testdata/comment_after_brace.jba | 18 + .../parser_testdata/comment_after_param.jba | 18 + ...omment_after_param_with_quotation_mark.jba | 18 + .../parser_testdata/comment_after_value.jba | 7 + ...omment_after_value_with_quotation_mark.jba | 7 + test/testdata/parser_testdata/description.jba | 7 + .../parser_testdata/description_invalid.jba | 7 + test/testdata/parser_testdata/empty_file.jba | 0 test/testdata/parser_testdata/empty_lines.jba | 12 + .../testdata/parser_testdata/empty_module.jba | 1 + .../empty_param_with_comment.jba | 6 + .../parser_testdata/equal_sign_in_param.jba | 6 + .../parser_testdata/equal_sign_only.jba | 7 + test/testdata/parser_testdata/global.jba | 7 + .../parser_testdata/global_overwrite.jba | 20 + .../parser_testdata/global_wrong_order.jba | 20 + test/testdata/parser_testdata/if_alias.jba | 6 + .../invalid_parameter_value.jba | 6 + .../invalid_variable_declaration1.jba | 7 + .../invalid_variable_declaration2.jba | 7 + .../invalid_variable_declaration3.jba | 7 + .../parser_testdata/keyword_sequence.jba | 6 + .../parser_testdata/leading_whitespace.jba | 6 + .../missing_beginning_percent_sign.jba | 7 + .../parser_testdata/missing_brace.jba | 6 + .../missing_closing_quotation_mark.jba | 6 + .../parser_testdata/missing_colon.jba | 6 + .../parser_testdata/missing_comma.jba | 12 + .../parser_testdata/missing_condition.jba | 5 + .../missing_ending_percent_sign.jba | 7 + .../parser_testdata/missing_equal_sign.jba | 7 + .../parser_testdata/missing_module_name.jba | 5 + .../missing_opening_quotation_mark.jba | 6 + .../parser_testdata/missing_parameter.jba | 6 + .../missing_parameter_value.jba | 6 + .../parser_testdata/missing_percent_signs.jba | 7 + .../missing_quotation_marks.jba | 6 + .../parser_testdata/missing_stepid.jba | 5 + .../parser_testdata/missing_stepid_nested.jba | 11 + .../parser_testdata/missing_variable.jba | 7 + .../missing_variable_equal_sign.jba | 7 + .../parser_testdata/missing_variable_name.jba | 7 + .../missing_variable_value.jba | 7 + .../parser_testdata/multi_invalid_empty.jba | 5 + .../parser_testdata/multi_invalid_eof.jba | 10 + .../multi_invalid_one_line_text_after_end.jba | 5 + .../multi_invalid_quotes_instead_of_ticks.jba | 8 + .../multi_invalid_text_after_end.jba | 8 + test/testdata/parser_testdata/multi_valid.jba | 14 + .../parser_testdata/multi_valid_one_line.jba | 5 + .../multiple_basic_modules.jba | 18 + .../multiple_basic_modules_nested.jba | 60 ++ .../parser_testdata/multiple_global.jba | 32 + .../multiple_global_wrong_order1.jba | 32 + .../multiple_global_wrong_order2.jba | 30 + .../multiple_module_declaration.jba | 8 + .../parser_testdata/multiple_param_false.jba | 8 + .../parser_testdata/multiple_param_true.jba | 8 + .../parser_testdata/multiple_params1.jba | 8 + .../parser_testdata/multiple_params2.jba | 8 + .../parser_testdata/multiple_params3.jba | 10 + .../parser_testdata/multiple_params4.jba | 8 + .../parser_testdata/multiple_params5.jba | 7 + .../parser_testdata/multiple_stepid.jba | 13 + .../multiple_stepid_declaration.jba | 7 + .../multiple_stepid_nested.jba | 12 + test/testdata/parser_testdata/name_alias.jba | 6 + .../parser_testdata/nested_global1.jba | 13 + .../parser_testdata/nested_global2.jba | 13 + .../parser_testdata/non_global_use_global.jba | 14 + .../one_quotation_mark_only.jba | 6 + .../parser_testdata/opening_brace_only.jba | 1 + .../overwrite_env_variable.jba | 6 + test/testdata/parser_testdata/param_alias.jba | 7 + .../parser_testdata/parameter_name_only.jba | 6 + .../parser_testdata/parameter_value_only.jba | 6 + test/testdata/parser_testdata/print.jba | 7 + .../parser_testdata/print_invalid.jba | 7 + .../redeclared_variable_same_module1.jba | 8 + .../redeclared_variable_same_module2.jba | 8 + .../requireselevatedprivileges_false1.jba | 7 + .../requireselevatedprivileges_false2.jba | 7 + .../requireselevatedprivileges_false3.jba | 7 + .../requireselevatedprivileges_invalid1.jba | 7 + .../requireselevatedprivileges_invalid2.jba | 7 + .../requireselevatedprivileges_true1.jba | 7 + .../requireselevatedprivileges_true2.jba | 7 + .../requireselevatedprivileges_true3.jba | 7 + .../round_bracket_in_param.jba | 6 + .../round_bracket_in_value.jba | 6 + .../parser_testdata/slashes_in_param.jba | 20 + .../slashes_in_param_with_comment.jba | 20 + ...n_param_with_quotation_mark_in_comment.jba | 20 + .../parser_testdata/slashes_in_value.jba | 7 + .../slashes_in_value_with_comment.jba | 7 + ...n_value_with_quotation_mark_in_comment.jba | 7 + .../parser_testdata/variable_name_only.jba | 7 + .../whitespace_in_module_name.jba | 6 + .../whitespace_in_param_name.jba | 6 + .../validator_testdata/existing_variable.jba | 6 + .../multiple_non_existing_variables.jba | 18 + .../validator_testdata/multiple_variables.jba | 18 + .../non_existing_variable.jba | 6 + .../windows_utility_testdata/secedit.cfg | Bin 0 -> 16410 bytes .../auditpolicyquery_test.go | 19 + test/windows_utility_test/regquery_test.go | 40 + test/windows_utility_test/secquery_test.go | 53 ++ util/logger/logger.go | 346 +++++++ util/permissions/unix_permissions.go | 49 + util/permissions/windows_permissions.go | 128 +++ util/privilege/privilege_detector_unix.go | 21 + util/privilege/privilege_detector_windows.go | 11 + util/unix_utility.go | 25 + util/utility.go | 264 ++++++ util/windows_utility.go | 91 ++ versioninfo.json | 30 + 231 files changed, 10069 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 auditorium/auditconfig/acutil/syntax.go create mode 100644 auditorium/auditconfig/acutil/util.go create mode 100644 auditorium/auditconfig/interpreter/audit_interpreter.go create mode 100644 auditorium/auditconfig/interpreter/audit_validator.go create mode 100644 auditorium/auditconfig/parser/parser.go create mode 100644 auditorium/auditconfig/syntaxchecker/syntaxchecker.go create mode 100644 auditorium/config/config-interpreter/config_interpreter.go create mode 100644 auditorium/config/config-parser/config_parser.go create mode 100644 auditorium/config/config-parser/config_parser_flags.go create mode 100644 auditorium/modulecontroller/module_controller.go create mode 100644 auditorium/modulecontroller/os_detector.go create mode 100644 auditorium/outputgenerator/output_generator.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 icon.ico create mode 100644 main.go create mode 100644 models/cli.go create mode 100644 models/dot_jba.go create mode 100644 models/module.go create mode 100644 modules/0_methodhandler.go create mode 100644 modules/0_template.go create mode 100644 modules/auditctl.go create mode 100644 modules/authselect.go create mode 100644 modules/awk_script.go create mode 100644 modules/bash_script.go create mode 100644 modules/check_partition.go create mode 100644 modules/execute_command.go create mode 100644 modules/file_content.go create mode 100644 modules/grep.go create mode 100644 modules/is_file.go create mode 100644 modules/is_installed.go create mode 100644 modules/is_not_installed.go create mode 100644 modules/modprobe.go create mode 100644 modules/nft_list_ruleset.go create mode 100644 modules/permissions.go create mode 100644 modules/script.go create mode 100644 modules/sshd.go create mode 100644 modules/stat.go create mode 100644 modules/sysctl.go create mode 100644 modules/systemctl.go create mode 100644 modules/win_audit_policy_query.go create mode 100644 modules/win_dump_security_settings.go create mode 100644 modules/win_export_installed_software.go create mode 100644 modules/win_get_account_name.go create mode 100644 modules/win_get_user_sid.go create mode 100644 modules/win_get_win_env.go create mode 100644 modules/win_is_gp_template_present.go create mode 100644 modules/win_registry_query.go create mode 100644 modules/win_security_settings_query.go create mode 100644 static/static.go create mode 100644 test/auditconfig_test/audit_interpreter_test.go create mode 100644 test/auditconfig_test/audit_parser_test.go create mode 100644 test/auditconfig_test/audit_validator_test.go create mode 100644 test/config_test/cli_auditcfg_test.go create mode 100644 test/config_test/cli_basic_param_test.go create mode 100644 test/config_test/cli_configini_commandline_test.go create mode 100644 test/config_test/cli_configini_test.go create mode 100644 test/config_test/cli_test.go create mode 100644 test/modules_test/script_test.go create mode 100644 test/outputgenerator_test/output_generator_test.go create mode 100644 test/testdata/cli_testdata/Test1/audit.jba create mode 100644 test/testdata/cli_testdata/Test1/audit_in_folder.jba create mode 100644 test/testdata/cli_testdata/Test1/folder_with_audit/cli_audit.jba create mode 100644 test/testdata/cli_testdata/Test10/config.ini create mode 100644 test/testdata/cli_testdata/Test11/config.ini create mode 100644 test/testdata/cli_testdata/Test12/audit.txt create mode 100644 test/testdata/cli_testdata/Test12/config.ini create mode 100644 test/testdata/cli_testdata/Test2/audit.jba create mode 100644 test/testdata/cli_testdata/Test2/audit_in_folder.jba create mode 100644 test/testdata/cli_testdata/Test2/config.ini create mode 100644 test/testdata/cli_testdata/Test2/folder_with_audit/config_audit.jba create mode 100644 test/testdata/cli_testdata/Test3/testauditxy.jba create mode 100644 test/testdata/cli_testdata/Test4/audit.jba create mode 100644 test/testdata/cli_testdata/Test4/audit_in_folder.jba create mode 100644 test/testdata/cli_testdata/Test5/audit_in_folder1.jba create mode 100644 test/testdata/cli_testdata/Test5/audit_in_folder2.jba create mode 100644 test/testdata/cli_testdata/Test7/audit1.jba create mode 100644 test/testdata/cli_testdata/Test7/audit2.jba create mode 100644 test/testdata/cli_testdata/Test7/config.ini create mode 100644 test/testdata/cli_testdata/Test7/folder_with_audit/audit_in_folder.jba create mode 100644 test/testdata/cli_testdata/Test8/testaudit.jba create mode 100644 test/testdata/cli_testdata/Test9/config.ini create mode 100644 test/testdata/cli_testdata/auditDummy.jba create mode 100644 test/testdata/cli_testdata/auditDummy2.jba create mode 100644 test/testdata/cli_testdata/basic_config.ini create mode 100644 test/testdata/cli_testdata/empty_lines.ini create mode 100644 test/testdata/cli_testdata/missing_key.ini create mode 100644 test/testdata/cli_testdata/missing_values.ini create mode 100644 test/testdata/cli_testdata/missing_values_except_loglevel.ini create mode 100644 test/testdata/cli_testdata/mixed_order.ini create mode 100644 test/testdata/cli_testdata/negative_loglevel.ini create mode 100644 test/testdata/cli_testdata/overwrite_audconf.ini create mode 100644 test/testdata/cli_testdata/overwrite_booleans.ini create mode 100644 test/testdata/cli_testdata/overwrite_output.ini create mode 100644 test/testdata/cli_testdata/overwrite_verbosity.ini create mode 100644 test/testdata/cli_testdata/spaces_and_quotation.ini create mode 100644 test/testdata/cli_testdata/static.ini create mode 100644 test/testdata/cli_testdata/static_local.ini create mode 100644 test/testdata/filme.txt create mode 100644 test/testdata/parser_testdata/additional_alias.jba create mode 100644 test/testdata/parser_testdata/additional_comma.jba create mode 100644 test/testdata/parser_testdata/backticks.jba create mode 100644 test/testdata/parser_testdata/basic_module.jba create mode 100644 test/testdata/parser_testdata/basic_module_nested.jba create mode 100644 test/testdata/parser_testdata/brace_in_param.jba create mode 100644 test/testdata/parser_testdata/brace_in_value.jba create mode 100644 test/testdata/parser_testdata/case_sensitivity1.jba create mode 100644 test/testdata/parser_testdata/case_sensitivity2.jba create mode 100644 test/testdata/parser_testdata/case_sensitivity_script1.jba create mode 100644 test/testdata/parser_testdata/case_sensitivity_script2.jba create mode 100644 test/testdata/parser_testdata/colon_in_value.jba create mode 100644 test/testdata/parser_testdata/colon_only.jba create mode 100644 test/testdata/parser_testdata/comment_after_brace.jba create mode 100644 test/testdata/parser_testdata/comment_after_param.jba create mode 100644 test/testdata/parser_testdata/comment_after_param_with_quotation_mark.jba create mode 100644 test/testdata/parser_testdata/comment_after_value.jba create mode 100644 test/testdata/parser_testdata/comment_after_value_with_quotation_mark.jba create mode 100644 test/testdata/parser_testdata/description.jba create mode 100644 test/testdata/parser_testdata/description_invalid.jba create mode 100644 test/testdata/parser_testdata/empty_file.jba create mode 100644 test/testdata/parser_testdata/empty_lines.jba create mode 100644 test/testdata/parser_testdata/empty_module.jba create mode 100644 test/testdata/parser_testdata/empty_param_with_comment.jba create mode 100644 test/testdata/parser_testdata/equal_sign_in_param.jba create mode 100644 test/testdata/parser_testdata/equal_sign_only.jba create mode 100644 test/testdata/parser_testdata/global.jba create mode 100644 test/testdata/parser_testdata/global_overwrite.jba create mode 100644 test/testdata/parser_testdata/global_wrong_order.jba create mode 100644 test/testdata/parser_testdata/if_alias.jba create mode 100644 test/testdata/parser_testdata/invalid_parameter_value.jba create mode 100644 test/testdata/parser_testdata/invalid_variable_declaration1.jba create mode 100644 test/testdata/parser_testdata/invalid_variable_declaration2.jba create mode 100644 test/testdata/parser_testdata/invalid_variable_declaration3.jba create mode 100644 test/testdata/parser_testdata/keyword_sequence.jba create mode 100644 test/testdata/parser_testdata/leading_whitespace.jba create mode 100644 test/testdata/parser_testdata/missing_beginning_percent_sign.jba create mode 100644 test/testdata/parser_testdata/missing_brace.jba create mode 100644 test/testdata/parser_testdata/missing_closing_quotation_mark.jba create mode 100644 test/testdata/parser_testdata/missing_colon.jba create mode 100644 test/testdata/parser_testdata/missing_comma.jba create mode 100644 test/testdata/parser_testdata/missing_condition.jba create mode 100644 test/testdata/parser_testdata/missing_ending_percent_sign.jba create mode 100644 test/testdata/parser_testdata/missing_equal_sign.jba create mode 100644 test/testdata/parser_testdata/missing_module_name.jba create mode 100644 test/testdata/parser_testdata/missing_opening_quotation_mark.jba create mode 100644 test/testdata/parser_testdata/missing_parameter.jba create mode 100644 test/testdata/parser_testdata/missing_parameter_value.jba create mode 100644 test/testdata/parser_testdata/missing_percent_signs.jba create mode 100644 test/testdata/parser_testdata/missing_quotation_marks.jba create mode 100644 test/testdata/parser_testdata/missing_stepid.jba create mode 100644 test/testdata/parser_testdata/missing_stepid_nested.jba create mode 100644 test/testdata/parser_testdata/missing_variable.jba create mode 100644 test/testdata/parser_testdata/missing_variable_equal_sign.jba create mode 100644 test/testdata/parser_testdata/missing_variable_name.jba create mode 100644 test/testdata/parser_testdata/missing_variable_value.jba create mode 100644 test/testdata/parser_testdata/multi_invalid_empty.jba create mode 100644 test/testdata/parser_testdata/multi_invalid_eof.jba create mode 100644 test/testdata/parser_testdata/multi_invalid_one_line_text_after_end.jba create mode 100644 test/testdata/parser_testdata/multi_invalid_quotes_instead_of_ticks.jba create mode 100644 test/testdata/parser_testdata/multi_invalid_text_after_end.jba create mode 100644 test/testdata/parser_testdata/multi_valid.jba create mode 100644 test/testdata/parser_testdata/multi_valid_one_line.jba create mode 100644 test/testdata/parser_testdata/multiple_basic_modules.jba create mode 100644 test/testdata/parser_testdata/multiple_basic_modules_nested.jba create mode 100644 test/testdata/parser_testdata/multiple_global.jba create mode 100644 test/testdata/parser_testdata/multiple_global_wrong_order1.jba create mode 100644 test/testdata/parser_testdata/multiple_global_wrong_order2.jba create mode 100644 test/testdata/parser_testdata/multiple_module_declaration.jba create mode 100644 test/testdata/parser_testdata/multiple_param_false.jba create mode 100644 test/testdata/parser_testdata/multiple_param_true.jba create mode 100644 test/testdata/parser_testdata/multiple_params1.jba create mode 100644 test/testdata/parser_testdata/multiple_params2.jba create mode 100644 test/testdata/parser_testdata/multiple_params3.jba create mode 100644 test/testdata/parser_testdata/multiple_params4.jba create mode 100644 test/testdata/parser_testdata/multiple_params5.jba create mode 100644 test/testdata/parser_testdata/multiple_stepid.jba create mode 100644 test/testdata/parser_testdata/multiple_stepid_declaration.jba create mode 100644 test/testdata/parser_testdata/multiple_stepid_nested.jba create mode 100644 test/testdata/parser_testdata/name_alias.jba create mode 100644 test/testdata/parser_testdata/nested_global1.jba create mode 100644 test/testdata/parser_testdata/nested_global2.jba create mode 100644 test/testdata/parser_testdata/non_global_use_global.jba create mode 100644 test/testdata/parser_testdata/one_quotation_mark_only.jba create mode 100644 test/testdata/parser_testdata/opening_brace_only.jba create mode 100644 test/testdata/parser_testdata/overwrite_env_variable.jba create mode 100644 test/testdata/parser_testdata/param_alias.jba create mode 100644 test/testdata/parser_testdata/parameter_name_only.jba create mode 100644 test/testdata/parser_testdata/parameter_value_only.jba create mode 100644 test/testdata/parser_testdata/print.jba create mode 100644 test/testdata/parser_testdata/print_invalid.jba create mode 100644 test/testdata/parser_testdata/redeclared_variable_same_module1.jba create mode 100644 test/testdata/parser_testdata/redeclared_variable_same_module2.jba create mode 100644 test/testdata/parser_testdata/requireselevatedprivileges_false1.jba create mode 100644 test/testdata/parser_testdata/requireselevatedprivileges_false2.jba create mode 100644 test/testdata/parser_testdata/requireselevatedprivileges_false3.jba create mode 100644 test/testdata/parser_testdata/requireselevatedprivileges_invalid1.jba create mode 100644 test/testdata/parser_testdata/requireselevatedprivileges_invalid2.jba create mode 100644 test/testdata/parser_testdata/requireselevatedprivileges_true1.jba create mode 100644 test/testdata/parser_testdata/requireselevatedprivileges_true2.jba create mode 100644 test/testdata/parser_testdata/requireselevatedprivileges_true3.jba create mode 100644 test/testdata/parser_testdata/round_bracket_in_param.jba create mode 100644 test/testdata/parser_testdata/round_bracket_in_value.jba create mode 100644 test/testdata/parser_testdata/slashes_in_param.jba create mode 100644 test/testdata/parser_testdata/slashes_in_param_with_comment.jba create mode 100644 test/testdata/parser_testdata/slashes_in_param_with_quotation_mark_in_comment.jba create mode 100644 test/testdata/parser_testdata/slashes_in_value.jba create mode 100644 test/testdata/parser_testdata/slashes_in_value_with_comment.jba create mode 100644 test/testdata/parser_testdata/slashes_in_value_with_quotation_mark_in_comment.jba create mode 100644 test/testdata/parser_testdata/variable_name_only.jba create mode 100644 test/testdata/parser_testdata/whitespace_in_module_name.jba create mode 100644 test/testdata/parser_testdata/whitespace_in_param_name.jba create mode 100644 test/testdata/validator_testdata/existing_variable.jba create mode 100644 test/testdata/validator_testdata/multiple_non_existing_variables.jba create mode 100644 test/testdata/validator_testdata/multiple_variables.jba create mode 100644 test/testdata/validator_testdata/non_existing_variable.jba create mode 100644 test/testdata/windows_utility_testdata/secedit.cfg create mode 100644 test/windows_utility_test/auditpolicyquery_test.go create mode 100644 test/windows_utility_test/regquery_test.go create mode 100644 test/windows_utility_test/secquery_test.go create mode 100644 util/logger/logger.go create mode 100644 util/permissions/unix_permissions.go create mode 100644 util/permissions/windows_permissions.go create mode 100644 util/privilege/privilege_detector_unix.go create mode 100644 util/privilege/privilege_detector_windows.go create mode 100644 util/unix_utility.go create mode 100644 util/utility.go create mode 100644 util/windows_utility.go create mode 100644 versioninfo.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61305f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.log +*.exe \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..3837ae9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Marius Schmalz, Tim Philipp, Felix Klör, Christian Höfig, Lukas Hagmaier, Tobias Nöth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c586312 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Jungbusch-Auditorium + +Dieses Repository enthält den Quellcode für das Jungbusch-Auditorium. Das Jungbusch-Auditorium ist ein Framework zum Erstellen Modularer System-Audits. + +## Download der Binaries + +Siehe [Releases](https://github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/releases) + +## Übersicht der Jungbusch-Repositories + +Siehe [Jungbusch-Overview](https://github.com/Jungbusch-Softwareschmiede/jungbusch-overview) + +## Handbuch + +Siehe [Jungbusch-Manual](https://github.com/Jungbusch-Softwareschmiede/jungbusch-manual) + +## Dokumentation + +Siehe [Jungbusch-Documentation](https://github.com/Jungbusch-Softwareschmiede/jungbusch-documentation) + +## About + +Dieses Projekt wurde im Rahmen des Projektsemesters im Studiengang [Cybersecurity](https://www.hs-mannheim.de/studieninteressierte/unsere-studiengaenge/bachelorstudiengaenge/cyber-security.html) an der [Hochschule Mannheim](https://www.hs-mannheim.de/) im Zeitraum von 03/2021 - 07/2021 entwickelt. + +### Mitwirkende + +[Christian Höfig](https://github.com/cookieChrissi) + +[Tim Philipp](https://github.com/TimPhi) + +[Marius Schmalz](https://github.com/ByteSizedMarius) + +[Felix Klör](https://github.com/prefixFelix) + +[Tobias Nöth](https://github.com/Tobias01101110) + +[Lukas Hagmaier](https://github.com/Lucky-180) + diff --git a/auditorium/auditconfig/acutil/syntax.go b/auditorium/auditconfig/acutil/syntax.go new file mode 100644 index 0000000..9fbbdfa --- /dev/null +++ b/auditorium/auditconfig/acutil/syntax.go @@ -0,0 +1,57 @@ +package acutil + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" +) + +// Diese Methode enthält in statischen Objekten die allgemeingültigen Parameter der Auditkonfiguration und deren Syntax. +func parameterSyntax() []ParameterSyntax { + return []ParameterSyntax{ // + { + ParamName: "stepid", + ParamDescription: "Mit diesem Parameter muss für jeden Schritt eine einzigartige ID festgelegt werden.", + ParamAlias: []string{"stepidentification", "id", "identifikation"}, + }, + { + ParamName: "module", + ParamDescription: "Mit diesem Parameter wird festgelegt, welches Modul ausgeführt werden soll.", + ParamAlias: []string{"mod", "modul"}, + }, + { + ParamName: "description", + ParamDescription: "Mit diesem Parameter kann eine Beschreibung für den auszuführenden Schritt definiert werden.", + ParamAlias: []string{"desc", "beschreibung", "definition"}, + }, + { + ParamName: "condition", + ParamDescription: "Mit diesem Parameter kann eine Bedingung festgelegt werden. Der aktuelle Schritt wird nur ausgeführt, wenn diese zutrifft.", + ParamAlias: []string{"cond", "bedingung"}, + }, + { + ParamName: "passed", + ParamDescription: "Mit diesem Parameter kann eine Bedingung festgelegt werden. Der Schritt wird als Passed markiert, wenn diese zutrifft.", + ParamAlias: []string{"bestanden", "erfolgreich"}, + }, + { + ParamName: "requireselevatedprivileges", + ParamDescription: "Mit diesem Parameter kann festgelegt werden, dass ein Modul Administrator, bzw. Root-Rechte benötigt.", + ParamAlias: []string{"requiresadmin", "requiresroot", "requiresprivileges", "rootonly", "adminonly"}, + }, + { + ParamName: "print", + ParamDescription: "Mit diesem Parameter kann eine Konsolen-, sowie eine zusätzliche Ausgabe im Result gemacht werden.", + ParamAlias: []string{"ausgeben"}, + }, + } +} + +// Diese Methode enthält den Syntax einer Bedingung. +func ifSyntax() []ParameterSyntax { + return []ParameterSyntax{ // + { + ParamName: "if", + ParamDescription: "Markiert den Start einer Bedingung.", + ParamAlias: []string{"wenn"}, + }, + } +} diff --git a/auditorium/auditconfig/acutil/util.go b/auditorium/auditconfig/acutil/util.go new file mode 100644 index 0000000..f43cee8 --- /dev/null +++ b/auditorium/auditconfig/acutil/util.go @@ -0,0 +1,357 @@ +// Dieses Package ist ein Utility-Package für das Validieren und Parsen der Audit-Konfiguration. +// Alle Methoden des Packages greifen ausschließlich auf die über die Methode SetLines gesetzte, rohe Audit-Konfigurationsdatei zu und sind anderweitig nicht zu verwenden. +package acutil + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "github.com/pkg/errors" + "regexp" + s "strings" +) + +var ( + lines []string // Die Audit-Konfigurationsdatei mit welcher gearbeitet wird +) + +// Setzt die Zeilen der Audit-Konfiguration, mit der die Methoden dieses Packages arbeiten. +func SetLines(l []string) { + lines = l +} + +// Konvertiert die Namen aller Variablen im übergebenen string zu lowercase und gibt das Ergebnis zurück. +func VariablesInStringToLower(in string) string { + variablesInParameter := GetVariablesInString(in) + for _, v := range variablesInParameter { + in = s.ReplaceAll(in, v, s.ToLower(v)) + } + return in +} + +// Sucht und returned alle Vorkommnisse von Variablen im übergebenen string +func GetVariablesInString(s string) []string { + reg, _ := regexp.Compile(static.REG_VARIABLE) + return reg.FindAllString(s, -1) +} + +// Diese Methode entfernt Inlinekommentare und Leerzeichen oder Tabs am Start des übergebenen strings und +// gibt das Ergebnis zurück. +func PrepareLine(line string) (string, error) { + line, err := RemoveInlineComment(Trim(line)) + if err != nil { + return "", err + } + + return Trim(line), nil +} + +// Liest die übergebene Auditkonfigurationsdatei ein. +func ReadAuditConfiguration(file string) (lines []string, err error) { + if file != "" { + Info("Die Datei " + file + " wird eingelesen.") + lines, err = util.ReadFile(file) + + if err != nil { + err = errors.New("Die Audit-Datei " + file + " konnte nicht eingelesen werden: " + err.Error()) + } + } else { + err = errors.New("Es wurde keine Audit-Konfigurationsdatei angegeben.") + } + return +} + +// Sucht anhand eines Keywords das passende ParameterSyntax-Objekt (allgemeingültig) und returned es oder ein leeres Objekt, +// falls nichts gefunden wurde. +func GetParameterSyntaxFromKeyword(keyword string) ParameterSyntax { + syntax := parameterSyntax() + + for n := range syntax { + if s.ToLower(syntax[n].ParamName) == s.ToLower(keyword) || util.ArrayContainsString(syntax[n].ParamAlias, keyword) { + return syntax[n] + } + } + return ParameterSyntax{} +} + +// Konvertiert einen Modulparameter-Alias in dessen Name anhand vom übergebenen Modul. +func GetModuleParameterSyntaxNameFromAlias(moduleSyntax ModuleSyntax, alias string) string { + for _, ms := range moduleSyntax.InputParams { + if s.ToLower(ms.ParamName) == s.ToLower(alias) || util.ArrayContainsString(ms.ParamAlias, alias) { + return ms.ParamName + } + } + return "" +} + +// Sucht in den übergebenen Modulen anhand des übergebenen Aliases nach dem Name des Moduls. +func GetModuleSyntaxFromNameOrAlias(name string, modules []ModuleSyntax) (ms ModuleSyntax) { + for n := range modules { + if s.ToLower(modules[n].ModuleName) == s.ToLower(name) || util.ArrayContainsString(modules[n].ModuleAlias, name) { + return modules[n] + } + } + return ms +} + +// True, wenn der übergebene String im Format einer Condition ist. +func IsCondition(in string) bool { + ifSyn := ifSyntax() + + condition := "^( *)(" + ifSyn[0].ParamName + ")( *)(\\()( *)([\"`])(.*)([\"`])( *)(\\))" + match, err := regexp.MatchString(condition, in) + + if err == nil && match { + return true + } + + for _, n := range ifSyn[0].ParamAlias { + // Genereller Syntax + condition = "^( *)(" + n + ")( *)(\\()( *)([\"`])(.*)([\"`])( *)(\\))" + match, err = regexp.MatchString(condition, in) + if err != nil { + return false + } + + // Der Syntax, der verwendet wird um die Condition zu finden + ifreg, err := regexp.Compile("(\\()( *)([\"`])(.*)([\"`])( *)(\\))") + if err != nil { + return false + } + + result := ifreg.FindString(in) + if match && len(result) > 4 { + return true + } + } + return false +} + +// Splittet eine Variable in Name und Wert. +func SplitVariable(in string) (string, string) { + return s.TrimSpace(Trim(in[:s.Index(in, "=")])), s.TrimSpace(Trim(in[s.Index(in, "=")+1:])) +} + +// Splittet einen Parameter in Name und Wert. Bei einem Multilineparameter wird nur die erste Zeile returned. +func SplitParameter(in string) (string, string) { + return s.TrimSpace(Trim(in[:s.Index(in, ":")])), s.TrimSpace(Trim(in[s.Index(in, ":")+1:])) +} + +// True, wenn der übergebene String dem Format eines Werts entspricht. (Umgeben von Anführungszeichen) +func IsValue(in string) bool { + in = s.TrimSpace(Trim(in)) + + // Jede Value muss entweder von Anführungszeichen oder Backticks umgeben sein + return (s.HasPrefix(in, "\"") && s.HasSuffix(in, "\"")) || + (s.HasPrefix(in, "`") && s.HasSuffix(in, "`")) +} + +// True, wenn der übergebene String dem Format einer Variable entspricht. (Also von Prozentzeichen umgeben) +func IsVariable(in string) bool { + in = s.TrimSpace(Trim(in)) + + // könnte mit static Variable Regex und Match besser gemacht werden + + // Jede Variable muss von Prozentzeichen umgeben sein + return s.HasPrefix(in, "%") && s.HasSuffix(in, "%") +} + +// True, wenn die Zeile an der übergebenen Zeilennummer dem Syntax eines Parameters entspricht. +func IsParameter(n *int) bool { + l := Trim(lines[*n]) + indexDoppelpunkt := s.Index(l, ":") + indexIstGleich := s.Index(l, "=") + + // Wenn kein =, aber ein : + return (indexIstGleich == -1 && indexDoppelpunkt != -1) || + // Oder = und :, aber : vor = + (indexIstGleich != -1 && indexDoppelpunkt != -1 && indexDoppelpunkt < indexIstGleich) +} + +// True, wenn die Zeile an der übergebenen Zeilennummer dem Syntax einer Variablen-Zuweisung entspricht. +func IsVariableAssignment(n *int) bool { + l := Trim(lines[*n]) + indexDoppelpunkt := s.Index(l, ":") + indexIstGleich := s.Index(l, "=") + + // Wenn kein :, aber ein = + return (indexIstGleich != -1 && indexDoppelpunkt == -1) || + // Oder = und :, aber : vor = + (indexIstGleich != -1 && indexDoppelpunkt != -1 && indexIstGleich < indexDoppelpunkt) +} + +// Überspringt für den Parser irrelevante Zeilen. +// Dies inkludiert leere Zeilen, Kommentare sowie Kommentarblöcke. +func SkipIrrelevantLines(lines []string, n *int) error { + var l string + + for ; *n < len(lines); *n++ { + // Leere Zeilen überspringen + SkipEmpty(n) + + if LinesFinished(n) { + return nil + } + + l = Trim(lines[*n]) + + // Kommentarblöcke überspringen + if IsCommentBlock(n) { + GoToEndOfCommentBlock(n) + if *n >= len(lines) { + return GenerateSyntaxError(static.COMMENTBLOCK_NEVER_CLOSED, *n+1, l, "") + } + continue + } else + + // Kommentare überspringen + if IsComment(n) { + continue + } else + // Kein Kommentar oder so mehr gefunden, die Schleife verlassen + { + break + } + } + + return nil +} + +// True, wenn die Konfigurationsdatei leer ist. +func AreLinesEmpty() bool { + return len(lines) == 0 || (len(lines) == 1 && Trim(lines[0]) == "") +} + +// True, wenn das Ende der Konfigurationsdatei erreicht wurde. +func LinesFinished(n *int) bool { + return *n >= len(lines) +} + +// Entfernt Inline-Kommentare aus dem übergebenen String. +func RemoveInlineComment(in string) (string, error) { + result := in + + // Wenn möglicherweise ein Kommentar vorkommt + if s.Contains(in, "//") { + + // Das erste Anführungszeichen finden + indexQuota := s.Index(in, "\"") + + // Den ersten Backtick finden + indexBacktick := s.Index(in, "`") + + // Der Wert steht in Anführungszeichen + if indexQuota != -1 && ((indexQuota < indexBacktick) || indexBacktick == -1) { + + // Die ersten zwei Anführungszeichen überspringen. -> Den Wert überspringen + in = in[s.Index(in, "\"")+1:] + in = in[s.Index(in, "\"")+1:] + + // Wenn immernoch ein // vorkommt, ist wahrscheinlich irgendwo ein Kommentar + if s.Contains(in, "//") { + + // Ich überprüfe nun, ob vor dem Kommentar noch ein Anführungszeichen ist + indexQuota = s.Index(in, "\"") + indexComment := s.Index(in, "//") + + // Wenn ja, ist in dieser Zeile irgendwas komisch + if indexQuota != -1 && indexQuota < indexComment { + return result, errors.New(static.INVALID_LINE + static.TOO_MANY_QUOTATIONMARKS) + } + + // Wenn nein, wird der Kommentar entfernt + result = result[:s.LastIndex(result, "//")] + } + } else if indexBacktick != -1 { + // Wenn wir ein Backtick gefunden haben, suchen wir nach dem zweiten Tick + in = in[s.Index(in, "`")+1:] + + // Wenn kein zweites gefunden wurde, haben wir einen Multiline-Parameter, da ignorieren wir inline-Kommentare + // Wenn doch, suchen wir den zweiten Backtick + if s.Contains(in, "`") { + in = in[s.Index(in, "`")+1:] + + // An dieser Stelle sollten wir den aktuellen Wert übersprungen haben + indexBacktick = s.Index(in, "`") + indexComment := s.Index(in, "//") + + // Wenn ein weiteres Tick vor dem Kommentar steht, ist etwas an der Zeile seltsam + if indexBacktick != -1 && indexBacktick < indexComment { + return result, errors.New(static.INVALID_LINE + static.TOO_MANY_TICKS) + } + + // Wenn das nicht der Fall ist und hinter den Backticks noch ein Kommentar steht, wird dieser entfernt + if s.Contains(in, "//") { + result = result[:s.LastIndex(result, "//")] + } + } + } else if s.Index(in, "\"") == -1 && s.Index(in, "`") == -1 { + result = result[:s.LastIndex(result, "//")] + } + } + + return s.Trim(Trim(result), " "), nil +} + +// True, wenn die Zeile an der übergebenen Zeilennummer ein Kommentar ist. +func IsComment(n *int) bool { + return s.HasPrefix(Trim(lines[*n]), "//") +} + +// Überspringt leere Zeilen. +func SkipEmpty(n *int) { + for len(lines) > *n && Trim(lines[*n]) == "" { + *n++ + } +} + +// True, wenn die Zeile an der übergebenen Zeilennummer ein Kommentarblock ist. +func IsCommentBlock(n *int) bool { + return s.HasPrefix(Trim(lines[*n]), "/*") +} + +// Überspringt einen Kommentarblock. +func GoToEndOfCommentBlock(n *int) { + l := Trim(lines[*n]) + + // Kommentarblock + if s.HasPrefix(l, "/*") { + + // Ende des Blocks suchen + for ; !s.HasSuffix(l, "*/"); { + if *n+1 >= len(lines) { + break + } + *n++ + l = Trim(lines[*n]) + } + } +} + +// Entfernt alle Leerzeichen und Tabs die vor dem übergebenen String stehen. +func Trim(in string) string { + for s.HasPrefix(in, " ") || s.HasPrefix(in, "\t") { + in = in[1:] + } + return in +} + +// Generiert einen Error des Typs models.SyntaxError aus den übergebenen Informationen. +func GenerateSyntaxError(errorMsg string, lineNo int, line string, keyword string) *SyntaxError { + return &SyntaxError{ + ErrorMsg: errorMsg, + LineNo: lineNo + 1, + Line: Trim(line), + Errorkeyword: keyword, + } +} + +// Versucht zu evaluieren, ob sich der Fehler an der übergebenen Zeile auf ein fehlendes Komma zurückführen lässt. +func EvaluateClosingBracketError(n *int) error { + l := Trim(lines[*n]) + if l == "}" { + return GenerateSyntaxError(static.MODULE_MISSING_CLOSING_BRACKET_COMMA, *n, l, "") + } + return GenerateSyntaxError(static.MODULE_MISSING_CLOSING_BRACKET, *n, l, "") +} diff --git a/auditorium/auditconfig/interpreter/audit_interpreter.go b/auditorium/auditconfig/interpreter/audit_interpreter.go new file mode 100644 index 0000000..cd4b761 --- /dev/null +++ b/auditorium/auditconfig/interpreter/audit_interpreter.go @@ -0,0 +1,349 @@ +package interpreter + +import ( + "errors" + "fmt" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/acutil" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/modulecontroller" + output "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/outputgenerator" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "github.com/dop251/goja" + "github.com/schollz/progressbar/v3" + "os" + "strconv" + "strings" +) + +// In diesem Struct werden die in Audit-Schritten verwendeten Variablen gespeichert. Dafür muss der originale Name +// (also mit %-Zeichen etc.) für später gespeichert werden und ein neuer Name, der dem ECMAScript-Syntax entspricht, +// erzeugt werden. +type moduleVariable struct { + name string + origName string + value string +} + +// Diese Funktion dient als eine Art Wrapper für das Interpretieren der Audit-Konfiguration. +// Es wird die Gültigkeit aller Variablen und Parameter überprüft, auch ob jeder Variable früher oder später +// ein Wert zugewiesen wird. Daraufhin werden alle Module ausgeführt und die Ergebnisse in einer Slice +// aus output.ReportEntry-Objekten zurückgegeben. +func InterpretAudit(audit []AuditModule, numberOfModules int, alwaysPrintProgress bool) (report []output.ReportEntry, err error) { + + // Parameter validieren, wenn irgendwas nicht stimmt, returnen + err = ValidateParameters(audit) + if err != nil { + return + } else { + Info("Alle Variablen und Modulparameter sind gültig.") + } + + Info(SeperateTitle("Audit-Interpreter")) + static.ProgressBar = progressbar.NewOptions(0, + progressbar.OptionShowCount(), + progressbar.OptionSetPredictTime(false), + progressbar.OptionClearOnFinish()) + static.ProgressBar.ChangeMax(numberOfModules) + + report = executeModules(audit, numberOfModules, alwaysPrintProgress) + return +} + +// executeModules führt die in der Audit-Datei festegelegten Module aus und gibt einen Report zurück. +// Zuächst wird überprüft, ob die zum Ausführen des Moduls benötigten Berechtigungen vorliegen. +// Anschließend wird die Ausführ-Bedingung überprüft. +// Ist diese erfüllt, so wird das Modul ausgeführt und das Ergebnis in ein output.ReportEntry-Objekt geschrieben. +func executeModules(modules []AuditModule, numberOfModules int, alwaysPrintProgress bool) []output.ReportEntry { + report := make([]output.ReportEntry, len(modules)) + globalVars := make([]Variable, 0) + + for i, m := range modules { + if m.Description == "" { + m.Description = fmt.Sprintf("%v-%v", m.StepID, m.ModuleName) + } + + // Anlegen des Report-Eintrags + report[i] = output.ReportEntry{ + ID: m.StepID, + Desc: m.Description, + } + + // Das Modul kann aufgrund von fehlender Berechtigungen nicht ausgeführt werden + if m.RequiresElevatedPrivileges && !static.HasElevatedPrivileges { + handleUnsuccessful(m, &report[i], alwaysPrintProgress, errors.New("Zur Ausführung des Moduls werden Administrator, bzw. Root-Privilegien benötigt.")) + continue + } + + // Setzen der globalen Variablen + for _, v := range globalVars { + m.Variables[v.Name] = v + } + + // Ausführbedingung überprüfen + ok, err := checkExecuteCondition(m) + if err != nil { + handleUnsuccessful(m, &report[i], alwaysPrintProgress, err) + continue + } + + // Wenn die Ausführbedingung nicht erfüllt ist, skippen + if !ok { + report[i].Result = "NOTEXECUTED" + Info(fmt.Sprintf(static.NOTEXECUTED_OUTPUT, m.Description)) + Debug(Seperate()) + _ = static.ProgressBar.Add(1) + addNotExecutedToProgressBar(m) + continue + } + + // Ersetzen von möglichen Variablen in den Parametern + for k, v := range m.ModuleParameters { + m.ModuleParameters[k] = replaceVariablesInString(v, m.Variables) + } + + // Ausführ-Bedingung ist erfüllt + Debug(fmt.Sprintf("Parameter: %v", m.ModuleParameters)) + + // Ausführen des Moduls und Erhalten des Ergebnisses + res, err := execute(m) + if err != nil { + // Wenn beim Ausführen des Moduls ein Fehler auftritt + handleUnsuccessful(m, &report[i], alwaysPrintProgress, err) + continue + } + Debug(fmt.Sprintf("Erhaltenes Ergebnis: %v", res.Result)) + + // Setzen der Ergebnis-Variable + m.Variables["%result%"] = Variable{ + Name: "%result%", + Value: res.Result, + IsEnv: true, + } + + // Weitergeben der Artefakte + report[i].Artifacts = res.Artifacts + + // Überprüfen der Passed-Bedingung + if err = setPassed(&m); err != nil { + handleUnsuccessful(m, &report[i], alwaysPrintProgress, err) + continue + } + + // Die Variablen für nachfolgende Module setzen + if err = setVariables(&m, &globalVars); err != nil { + handleUnsuccessful(m, &report[i], alwaysPrintProgress, err) + continue + } + + // Wenn der Print-Parameter nicht leer ist, ersetzen wir alle enthaltenen Variablen und geben ihn aus + if m.Print != "" { + m.Print = replaceVariablesInString(m.Print, m.Variables) + InfoPrintAlways(m.Print) + report[i].Print = m.Print + } + + // Ergebnis der Passed-Bedingung überprüfen, in den Report eintragen und Konsolenausgaben + passed, err := strconv.ParseBool(m.Variables["%passed%"].Value) + if err != nil { + handleUnsuccessful(m, &report[i], alwaysPrintProgress, err) + continue + } + + if passed { + // Bedingung ist erfüllt + InfoPrint(fmt.Sprintf(static.PASSED_OUTPUT, m.Description), alwaysPrintProgress) + Debug(Seperate()) + _ = static.ProgressBar.Add(1) + report[i].Result = "PASSED" + } else { + // Bedingung ist nicht erfüllt + InfoPrint(fmt.Sprintf(static.NOTPASSED_OUTPUT, m.Description), alwaysPrintProgress) + Debug(Seperate()) + _ = static.ProgressBar.Add(1) + report[i].Result = "NOTPASSED" + report[i].Expected = m.Passed + report[i].Actual = m.Variables["%result%"].Value + if report[i].Actual == "" { + report[i].Actual = "null" + } + } + + // Rekursives Ausführen der verschachtelten Module + report[i].Nested = executeModules(m.NestedModules, numberOfModules, alwaysPrintProgress) + } + + return report +} + +// Übernimmt das Zusammenbauen eines Report-Entries für einen Audit-Schritt, in dem ein Fehler aufgetreten ist. +func handleUnsuccessful(m AuditModule, reportEntry *output.ReportEntry, alwaysPrintProgress bool, err error) { + InfoPrint(fmt.Sprintf(static.UNSUCCESSFUL_OUTPUT, m.Description), alwaysPrintProgress) + reportEntry.Result = "UNSUCCESSFUL" + reportEntry.Expected = m.Passed + reportEntry.Actual = m.Variables["%result%"].Value + if reportEntry.Actual == "" { + reportEntry.Actual = "null" + } + reportEntry.Error = err.Error() + m.Variables["%unsuccessful%"] = Variable{ + Name: "%unsuccessful%", + Value: "true", + IsEnv: true, + } + + Debug("Modul fehlgeschlagen: " + err.Error()) + Debug(Seperate()) + _ = static.ProgressBar.Add(1) + addNotExecutedToProgressBar(m) +} + +// Überprüft die Ausführ-Bedingung, des Moduls, wenn angegeben +func checkExecuteCondition(m AuditModule) (bool, error) { + if m.Condition != "" { + ok, err := interpretCondition(m.Condition, m.Variables) + if err != nil { + return false, err + } + Debug(fmt.Sprintf(`Ergebnis der Ausführ-Bedingung "%v": %v`, m.Condition, ok)) + return ok, nil + } else { + return true, nil + } +} + +// Führt die Execute-Methode des übergebenen Moduls aus. +func execute(m AuditModule) (res ModuleResult, err error) { + res = modulecontroller.Call(m.ModuleName, m) + if res.Err != nil { + return ModuleResult{Artifacts: res.Artifacts}, res.Err + } + res.Result = strings.TrimSuffix(res.Result, "\n") + return res, nil +} + +// Interpretiert die Passed-Condition, macht Konsolenausgaben und setzt Umgebungsvariablen +func setPassed(m *AuditModule) error { + passed, err := interpretCondition(m.Passed, m.Variables) + if err != nil { + return err + } + Debug(fmt.Sprintf(`Erhaltenes Ergebnis für "%v": %v`, m.Passed, passed)) + m.Variables["%passed%"] = Variable{ + Name: "%passed%", + Value: strconv.FormatBool(passed), + IsEnv: true, + } + return nil +} + +// Iteriert über die Variablen des übergebenen Moduls. Die Werte aller Variablen, die keine Umgebungsvariablen sind, +// werden an die Kinder des Moduls weitergegeben, da Variablen des Elternteils dort ebenfalls zugreifbar sind. +func setVariables(m *AuditModule, globalVars *[]Variable) error { + for _, v := range m.Variables { + if !v.IsEnv { + // Bezieht sich die Variable auf eine andere wird hier ihr Wert korrekt gesetzt + if _, ok := m.Variables[v.Value]; ok { + m.Variables[v.Name] = Variable{ + Name: v.Name, + Value: m.Variables[v.Value].Value, + } + } + // Setzen der Variable in Kind-Modulen + for _, nm := range m.NestedModules { + nm.Variables[v.Name] = m.Variables[v.Name] + } + } + + // Speichern der globalen Variablen + if v.IsGlobal && m.Variables["%passed%"].Value == "true" { + *globalVars = append(*globalVars, m.Variables[v.Name]) + } + } + + return nil +} + +// Interpretiert die übergebene Bedingung mithilfe von GOJA unter Verwendung aller übergebenen Variablen. +func interpretCondition(cond string, varMap VariableMap) (bool, error) { + vm := goja.New() + vars := extractVars(cond) + + // Entfernen der '%'-Zeichen von Variablen in der Condition, damit sie JS-Syntax entspricht + cond = strings.ReplaceAll(cond, "%", "") + if err := setVarValues(vars, varMap); err != nil { + return false, errors.New("GOJA-Error: " + err.Error()) + } + + // Exportiert alle gefundenen Variablen in die JS-VM + for _, v := range vars { + if err := vm.Set(v.name, util.CastToAppropriateType(v.value)); err != nil { + return false, err + } + } + + v, err := vm.RunString(cond) + if err != nil { + return false, errors.New("GOJA-Error: " + err.Error()) + } + + return v.ToBoolean(), nil +} + +// extractVars extrahiert alle Variablen aus der Condition +// und gibt sie als Array zurück +func extractVars(cond string) (v []*moduleVariable) { + vars := acutil.GetVariablesInString(cond) + + for i := range vars { + vars[i] = strings.Trim(vars[i], "%") + v = append(v, &moduleVariable{ + name: vars[i], + origName: strings.ToLower("%" + vars[i] + "%"), + }) + } + return +} + +// setVarValues weist den extrahierten Variablen den passenden Wert aus dem Modul zu +func setVarValues(vars []*moduleVariable, varMap VariableMap) error { + for _, v := range vars { + if mvar, ok := varMap[v.origName]; ok { + v.value = mvar.Value + } else { + return errors.New(fmt.Sprintf("variable '%v' does not exist", v.origName)) + } + } + return nil +} + +// Fügt Schritte, die, warum auch immer nicht ausgeführt, z.B. weil das Elternmodul fehlgeschlafen ist, werden der Progressbar als "fertig" zu. +func addNotExecutedToProgressBar(m AuditModule) { + for _, nested := range m.NestedModules { + _ = static.ProgressBar.Add(1) + addNotExecutedToProgressBar(nested) + } + return +} + +// Ersetzt alle Variablen im übergebenenen String mit dem Wert der passenden Variable, wenn in der übergebenenen Map vorhanden. Löst außerdem Pfade auf. +func replaceVariablesInString(s string, variables VariableMap) string { + vars := acutil.GetVariablesInString(s) + + for i, v := range vars { + vars[i] = strings.ToLower(v) + s = strings.ReplaceAll(s, v, vars[i]) + } + + for _, v := range vars { + if path, err := util.GetAbsolutePath(variables[v].Value); err == nil { + if _, err := os.Stat(path); err == nil { + s = strings.ReplaceAll(s, v, path) + } else { + s = strings.ReplaceAll(s, v, variables[v].Value) + } + } + } + return s +} diff --git a/auditorium/auditconfig/interpreter/audit_validator.go b/auditorium/auditconfig/interpreter/audit_validator.go new file mode 100644 index 0000000..908b4b9 --- /dev/null +++ b/auditorium/auditconfig/interpreter/audit_validator.go @@ -0,0 +1,67 @@ +package interpreter + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/acutil" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/modulecontroller" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/pkg/errors" + "strings" +) + +// Dient als "Start-Funktion". Ruft validateParametersInModule für alle Level-0 Module auf +func ValidateParameters(mods []AuditModule) error { + + // Variablen validieren + if err := validateVariables(mods); err != nil { + return err + } + + // Parameter validieren + if err := modulecontroller.ValidateAuditModules(mods); err != nil { + return err + } + + return nil +} + +// Überprüft, ob alle referenzierten Variablen vor Verwendung deklariert wurden +func validateVariables(modules []AuditModule) error { + for _, m := range modules { + // Alle Variablen in Print, Passed, Condition und Modul-Parametern finden + vars := acutil.GetVariablesInString(m.Print) + vars = append(vars, acutil.GetVariablesInString(m.Condition)...) + vars = append(vars, acutil.GetVariablesInString(m.Passed)...) + + for _, param := range m.ModuleParameters { + vars = append(vars, acutil.GetVariablesInString(param)...) + } + + // Überprüfen, ob alle gefundenen Variablen deklariert wurden + for _, v := range vars { + if _, ok := m.Variables[strings.ToLower(v)]; !strings.HasPrefix(v, "%g_") && !ok { + return errors.New("Variable " + strings.ToLower(v) + " wurde nicht deklariert!") + } + } + + for _, v := range m.Variables { + // Umgebungsvariablen haben immer einen Wert + if !v.IsEnv { + + // Wenn die aktuelle Variable auf eine unbekannte Variable verweist, ist das ungültig + v.Value = strings.ToLower(v.Value) + if _, ok := m.Variables[v.Value]; acutil.IsVariable(v.Value) && !ok { + return errors.New("Die Variable " + v.Name + " verweist auf eine unbekannte Variable: " + v.Value) + } + + // Setzen der Variable in Kind-Modulen + for _, nm := range m.NestedModules { + nm.Variables[v.Name] = m.Variables[v.Name] + } + } + } + if err := validateVariables(m.NestedModules); err != nil { + return err + } + } + return nil +} diff --git a/auditorium/auditconfig/parser/parser.go b/auditorium/auditconfig/parser/parser.go new file mode 100644 index 0000000..96fba14 --- /dev/null +++ b/auditorium/auditconfig/parser/parser.go @@ -0,0 +1,421 @@ +// Dieses Package ist für das Einlesen der Auditkonfigurations-Datei in models.AuditModule -Objekte zuständig. +// Sie ist darauf angewiesen, dass vorher der Syntaxchecker erfolgreich durchgelaufen ist und somot alle +// möglichen Syntaxerror abgefangen wurden. Ist dies nicht der Fall und enthält die Auditkonfigurationsdatei +// Fehler, dann ist das Auftreten eines Panics in diesem Package wahrscheinlich. +package parser + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/acutil" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" + "reflect" + "regexp" + s "strings" +) + +var ( + processRequiresElevatedPrivileges bool // Speichert ob der Prozess Administratorprivilegien benötigt. + numberOfModules int // Enthält die gesamte Anzahl der eingelesenen Module. Wird für die Progressbar benötigt. + usedIds map[string]bool // Mit dieser Map wird gespeichert, welche IDs für die Module bereits verwendet wurde. + loadedModules []ModuleSyntax // In dieser Map werden die geladenen Modulen für den Zugriff aus dem gesamten Package gespeichert. +) + +// Liest die übergebene Auditkonfigurations-Datei anhand der übergebenen geladenen Modulen ein. +// Gibt eine Slice aller Audit-Schritte, ob der Prozess Administratorberechtigungen benötigt, die gesamte +// Anzahl der Module und gegebenenfalls einen Error zurück. +func Parse(lines []string, moduleSyntax []ModuleSyntax) ([]AuditModule, bool, int, error) { + modules := make([]AuditModule, 0) + usedIds = make(map[string]bool, 0) + n := new(int) + loadedModules = moduleSyntax + var err error + var l string + var currentModule AuditModule + + // Die zu verwendenden Zeilen für die Utility setzen + SetLines(lines) + + // Alle Zeilen iterieren + for ; *n < len(lines); *n++ { + // Kommentare, leere Zeilen überspringen + if err = SkipIrrelevantLines(lines, n); err != nil { + return nil, false, 0, err + } + + if LinesFinished(n) { + break + } + + // Entfernt den Inline-Kommentar aus der aktuellen Zeile, wenn vorhanden + l, err = RemoveInlineComment(Trim(lines[*n])) + if err != nil { + return nil, false, 0, GenerateSyntaxError(err.Error(), *n, l, "") + } + + // An dieser Stelle muss jetzt ein Modul geöffnet werden + if l == "{" { + currentModule, err = generateModule(lines, n) + if err != nil { + return nil, false, 0, err + } + modules = append(modules, currentModule) + numberOfModules++ + } else { + return nil, false, 0, GenerateSyntaxError(static.MODULE_MISSING_OPENING_BRACKET, *n, l, "") + } + } + + return modules, processRequiresElevatedPrivileges, numberOfModules, validateGlobals(modules) +} + +// Generiert ein einzelnes Modul. Wird rekursiv aufgerufen. +func generateModule(lines []string, n *int) (AuditModule, error) { + var err error + *n++ + + // Ein neues Audit-Modul mit einigen Default-Werten generieren, welches jetzt gefüllt werden soll. + currentModule := initializeAuditModule() + + if LinesFinished(n) { + return AuditModule{}, GenerateSyntaxError(static.MODULE_EMPTY, *n, lines[len(lines)-1], "") + } + l := Trim(lines[*n]) + + // Zeilen iterieren bis das aktuelle Modul endet + for err == nil && l != "}," { + // Überprüfen, ob die Datei zu Ende ist + if LinesFinished(n) { + return AuditModule{}, EvaluateClosingBracketError(n) + } + + // Kommentare, leere Zeilen überspringen + err = SkipIrrelevantLines(lines, n) + l, err = PrepareLine(lines[*n]) + if err != nil { + return AuditModule{}, GenerateSyntaxError(err.Error(), *n, l, "") + } + + // Fehlendes Komma bei der schließenden Klammer abfangen + if l == "}" { + return AuditModule{}, EvaluateClosingBracketError(n) + } + + // Verschachteltes Modul + if l == "{" { + mod, err := generateModule(lines, n) + if err != nil { + return AuditModule{}, err + } + currentModule.NestedModules = append(currentModule.NestedModules, mod) + numberOfModules++ + *n++ + l, err = PrepareLine(lines[*n]) + continue + } + + // Die Zeile enthält einen Parameter + if IsParameter(n) { + if err = handleParameter(lines, n, ¤tModule); err != nil { + return AuditModule{}, err + } + *n++ + l, err = PrepareLine(lines[*n]) + continue + } else + + // Die Zeile enthält eine Variable + if IsVariableAssignment(n) { + if err = handleVariable(lines, n, ¤tModule); err != nil { + return AuditModule{}, err + } + *n++ + l, err = PrepareLine(lines[*n]) + continue + } + + // Ungültiger Ausdruck + return AuditModule{}, GenerateSyntaxError(static.MODULE_INVALID_EXPRESSION, *n, l, "") + } + + return currentModule, validateCompletenessOfModule(¤tModule, lines, n) +} + +// Überprüft, ob alle nötigen Werte im übergebenen Schritt gesetzt sind. +func validateCompletenessOfModule(module *AuditModule, lines []string, n *int) error { + + // Checken ob zwingend zu setzende Parameter fehlen + missingParameters := "" + if len(s.Trim(s.TrimSpace(Trim(module.ModuleName)), "\t")) == 0 { + missingParameters += "Modul-Name " + } + + if len(s.Trim(s.TrimSpace(Trim(module.StepID)), "\t")) == 0 { + missingParameters += "Step-ID " + } + + if missingParameters != "" { + return GenerateSyntaxError(static.MODULE_MISSING_PARAMETER+missingParameters+". "+static.ROW_IS_END_OF_MODULE, *n, lines[*n], "") + } + + // Den Blueprint des aktuellen Moduls finden + moduleSyntax := GetModuleSyntaxFromNameOrAlias(module.ModuleName, loadedModules) + + // Checken ob der Modulname/ das Modul existiert + if moduleSyntax.ModuleName == "" { + return GenerateSyntaxError(static.MODULE_NOT_FOUND+static.ROW_IS_END_OF_MODULE, *n, lines[*n], "") + } + + // Falls ein Alias gesetzt war, mit dem Modulname austauschen + module.ModuleName = moduleSyntax.ModuleName + + // Umgebungsvariable für das Modul setzen + cm := module.Variables["%currentmodule%"] + cm.Value = module.ModuleName + module.Variables["%currentmodule%"] = cm + + // Die Keys aus der Modul-Parametermap extrahieren + keys := reflect.ValueOf(module.ModuleParameters).MapKeys() + + // Alle Modul-Parameter-Aliase zu den korrekten Modul-Parameter-Namen konvertieren + for _, v := range keys { + paramAlias := v.String() + param := GetModuleParameterSyntaxNameFromAlias(moduleSyntax, paramAlias) + + if param == "" { + return GenerateSyntaxError(static.MODULE_INVALID_PARAMETER+static.ROW_IS_END_OF_MODULE, *n, lines[*n], paramAlias) + } + + // Wenn in der Audit-Konfiguration für den Modulparameter ein Alias angegeben war, + // setzen wir den Wert in der Map an die Stelle des Parameternamens + if param != paramAlias { + module.ModuleParameters[param] = module.ModuleParameters[paramAlias] + module.ModuleParameters[paramAlias] = "" + } + } + + // Überprüfen ob alle zwingend benötigten Modul-Parameter gesetzt wurden und ob sie Multiline sind oder nicht + for _, m := range moduleSyntax.InputParams { + // Wenn Parameter nicht optional, checken ob gesetzt + if !m.IsOptional { + if module.ModuleParameters[m.ParamName] == "" { + return GenerateSyntaxError(static.MODULE_REQUIRED_MODULE_PARAMETER_NOT_SET+m.ParamName+". "+static.ROW_IS_END_OF_MODULE, *n, lines[*n], m.ParamName) + } + } + } + + // Wenn die Privilegien in der Audit-Konfig nicht überschrieben wurden, holen wir uns sie aus dem Modul-Syntax + if !module.PrivilegesOverwritten { + module.RequiresElevatedPrivileges = moduleSyntax.RequiresElevatedPrivileges + processRequiresElevatedPrivileges = module.RequiresElevatedPrivileges || processRequiresElevatedPrivileges + } + + // Wenn Passed nicht gesetzt wurde, ist es true by default + if module.Passed == "" { + module.Passed = "true" + } + + return nil +} + +// Übernimmt das extrahieren, konvertieren und validieren eines Parameters aus der übergebenen Zeile. +func handleParameter(lines []string, n *int, currentModule *AuditModule) error { + // Error wurde im Syntaxcheck abgefangen + line, _ := RemoveInlineComment(lines[*n]) + name, value := SplitParameter(line) + + // Multiline behandeln + if s.HasPrefix(value, "`") { + + if s.Index(value, "`") == s.LastIndex(value, "`") { + *n++ + line, _ = PrepareLine(lines[*n]) + value = value + "\n" + line + } + + for ; !s.HasSuffix(line, "`"); { + *n++ + line, _ = RemoveInlineComment(lines[*n]) + value = value + "\n" + line + } + } + value = VariablesInStringToLower(value) + + p := Parameter{ParamName: name, ParamValue: value} + pSyntax := GetParameterSyntaxFromKeyword(p.ParamName) + + // Durch alle allgemeinbekannten Syntaxe iterieren und den Wert validieren + switch s.ToLower(pSyntax.ParamName) { + + case "condition": + condition := getConditionStringFromString(p.ParamValue) + if err := setValue(¤tModule.Condition, condition); err != nil { + return GenerateSyntaxError(err.Error(), *n, lines[*n], "") + } + + case "module": + if err := setValue(¤tModule.ModuleName, p.ParamValue); err != nil { + return GenerateSyntaxError(err.Error(), *n, lines[*n], "") + } + + case "description": + if err := setValue(¤tModule.Description, p.ParamValue); err != nil { + return GenerateSyntaxError(err.Error(), *n, lines[*n], "") + } + + case "passed": + condition := getConditionStringFromString(p.ParamValue) + if err := setValue(¤tModule.Passed, condition); err != nil { + return GenerateSyntaxError(err.Error(), *n, lines[*n], "") + } + + case "stepid": + if !usedIds[p.ParamValue] { + if err := setValue(¤tModule.StepID, p.ParamValue); err != nil { + return GenerateSyntaxError(err.Error(), *n, lines[*n], "") + } + usedIds[p.ParamValue] = true + } else { + return GenerateSyntaxError(static.MODULE_VALUE_INVALID+static.ID_ALREADY_USED, *n, lines[*n], p.ParamValue) + } + + case "requireselevatedprivileges": + if !currentModule.PrivilegesOverwritten { + currentModule.PrivilegesOverwritten = true + processRequiresElevatedPrivileges = true + + boolean, err := util.ParseStringToBool(s.Trim(p.ParamValue, "\"")) + if err != nil { + return GenerateSyntaxError(static.MODULE_VALUE_INVALID+static.INVALID_VALUE_FOR_BOOL, *n, lines[*n], p.ParamValue) + } + currentModule.RequiresElevatedPrivileges = boolean + } else { + return GenerateSyntaxError(static.MODULE_VALUE_ALREADY_SET, *n, lines[*n], "") + } + + case "print": + if err := setValue(¤tModule.Print, p.ParamValue); err != nil { + return err + } + + default: + // Es liegt ein Modul-Parameter vor + if s.HasPrefix(p.ParamValue, "`") && s.HasSuffix(p.ParamValue, "`") { + p.ParamValue = s.Trim(p.ParamValue, "`") + } else if s.HasPrefix(p.ParamValue, "\"") && s.HasSuffix(p.ParamValue, "\"") { + p.ParamValue = s.Trim(p.ParamValue, "\"") + } + + if currentModule.ModuleParameters[p.ParamName] != "" { + return GenerateSyntaxError(static.MODULE_VALUE_ALREADY_SET, *n, lines[*n], "") + } + currentModule.ModuleParameters[p.ParamName] = p.ParamValue + } + + return nil +} + +// Übernimmt das extrahieren, konvertieren und validieren einer Variable aus der übergebenen Zeile. +func handleVariable(lines []string, n *int, currentModule *AuditModule) error { + // Error wurde im Syntaxcheck abgefangen + line, _ := RemoveInlineComment(lines[*n]) + name, value := SplitVariable(line) + + // Variablen sind nicht Case-Sensitiv, daher mache ich alle Variablen lowercase + name = s.ToLower(name) + if IsVariable(value) { + value = s.ToLower(value) + } else { + value = s.Trim(value, "\"") + } + + // Umgebungsvariablen dürfen nicht überschrieben werden + if currentModule.Variables[name].IsEnv { + return GenerateSyntaxError(static.VARIABLE_INVALID_NAME+static.ENVIRONMENT_VARIABLES_READONLY, *n, lines[*n], name) + } + + // Variablen dürfen pro Modul nur einmal gesetzt werden + if currentModule.Variables[name].Name != "" { + return GenerateSyntaxError(static.VARIABLE_INVALID+static.VARIABLE_ALREADY_SET, *n, lines[*n], name) + } + + isGlobal := s.HasPrefix(name, "%g_") + if isGlobal { + currentModule.IsGlobal = true + } + + currentModule.Variables[name] = Variable{ + Name: name, + Value: value, + IsGlobal: isGlobal, + } + + return nil +} + +// Validiert globale Variablen, in allen übergebenen Auditschritten und überprüft ob sie an der richtigen Stelle +// deklariert sind und dem geforderten Syntax entsprechen. +func validateGlobals(mods []AuditModule) error { + prev := mods[0] + + for _, m := range mods { + if m.IsGlobal { + + // Globale Module dürfen keine Verschachtelung haben + if len(m.NestedModules) != 0 { + return errors.New(static.GLOBAL_MODULES_NOT_ALLOWED_NESTED + m.StepID) + } + + if !prev.IsGlobal { + // Das aktuelle Modul ist Global, das vorherige nicht + return errors.New(static.GLOBAL_VARIABLE_READONLY + m.StepID) + } + } else { + for _, nest := range m.NestedModules { + if nest.IsGlobal { + return errors.New(static.GLOBAL_MODULE_NESTED + m.StepID) + } + } + } + prev = m + } + + return nil +} + +// Setzt den übergebenen Wert in den übergebenen Pointer und überprüft ob in den Pointer bereits etwas gesetzt wurde. +func setValue(pointerToSetIn *string, valueToSet string) error { + if *pointerToSetIn == "" { + + if s.HasPrefix(valueToSet, "\"") && s.HasSuffix(valueToSet, "\"") { + *pointerToSetIn = Trim(s.Trim(valueToSet, "\"")) + } else if s.HasPrefix(valueToSet, "`") && s.HasSuffix(valueToSet, "`") { + *pointerToSetIn = Trim(s.Trim(valueToSet, "`")) + } else { + *pointerToSetIn = valueToSet + } + } else { + return errors.New(static.MODULE_VALUE_ALREADY_SET) + } + return nil +} + +// Returned den Condition-String aus dem übergebenen String. Klammern und Anführungszeichen werden entfernt. +func getConditionStringFromString(condition string) string { + // Errors hat der Syntaxchecker abgefangen + ifreg, _ := regexp.Compile("(\\()( *)([\"`])(.*)([\"`])( *)(\\))") + result := ifreg.FindString(condition) + return result[1 : len(result)-1] +} + +// Diese Methode initialisiert AuditModules mit ihren im Interpreter zu setzenden Environment-Variables +func initializeAuditModule() AuditModule { + varSlice := make(VariableMap) + varSlice["%result%"] = Variable{Name: "%result%", IsEnv: true} + varSlice["%passed%"] = Variable{Name: "%passed%", IsEnv: true} + varSlice["%unsuccessful%"] = Variable{Name: "%unsuccessful%", IsEnv: true} + varSlice["%os%"] = Variable{Name: "%os%", Value: static.OperatingSystem, IsEnv: true} + varSlice["%currentmodule%"] = Variable{Name: "%currentmodule%", IsEnv: true} + return AuditModule{Variables: varSlice, ModuleParameters: ParameterMap{}} +} diff --git a/auditorium/auditconfig/syntaxchecker/syntaxchecker.go b/auditorium/auditconfig/syntaxchecker/syntaxchecker.go new file mode 100644 index 0000000..262530d --- /dev/null +++ b/auditorium/auditconfig/syntaxchecker/syntaxchecker.go @@ -0,0 +1,285 @@ +// Dieses Package ist für das Überprüfen des Syntaxes der Audit-Konfigurationsdatei zuständig. +package syntaxchecker + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/acutil" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/pkg/errors" + "regexp" + s "strings" +) + +// Diese Methode überprüft den Syntax der übergebenen Audit-Konfigurationsdatei. +// Sie gibt einen Error zurück, der in den Typ models.SyntaxError gecasted werden kann. +func Syntax(lines []string) error { + n := new(int) + var err error + var l string + SetLines(lines) + + // Überprüfen, ob die Datei leer ist + if AreLinesEmpty() { + return GenerateSyntaxError(static.AUDIT_CONFIG_EMPTY, -1, "", "") + } + + // Alle Zeilen iterieren + for ; *n < len(lines); *n++ { + + // Kommentare, leere Zeilen überspringen + if err = SkipIrrelevantLines(lines, n); err != nil { + return err + } + + if LinesFinished(n) { + break + } + + l, err = RemoveInlineComment(Trim(lines[*n])) + if err != nil { + return GenerateSyntaxError(err.Error(), *n, l, "") + } + + // An dieser Stelle muss jetzt ein Modul geöffnet werden + if l == "{" { + if err = syntaxCheckModule(lines, n); err != nil { + return err + } + } else { + return GenerateSyntaxError(static.MODULE_MISSING_OPENING_BRACKET, *n, l, "") + } + } + + return nil +} + +// Diese Methode überprüft den Syntax eines einzelnen Moduls. Sie wird rekursiv für verschachtelte Module aufgerufen. +func syntaxCheckModule(lines []string, n *int) (err error) { + + // Überprüfen, ob die Datei zu Ende ist + *n++ + if LinesFinished(n) { + return GenerateSyntaxError(static.MODULE_EMPTY, *n, lines[len(lines)-1], "") + } + l := Trim(lines[*n]) + + // Zeilen iterieren bis das aktuelle Modul endet + for err == nil && l != "}," { + + // Überprüfen, ob die Datei zu Ende ist + if LinesFinished(n) { + return EvaluateClosingBracketError(n) + } + + // Kommentare, leere Zeilen überspringen + err = SkipIrrelevantLines(lines, n) + l, err = PrepareLine(lines[*n]) + if err != nil { + return GenerateSyntaxError(err.Error(), *n, l, "") + } + + // Fehlendes Komma bei der schließenden Klammer abfangen + if l == "}" { + return EvaluateClosingBracketError(n) + } + + // Verschachteltes Modul + if l == "{" { + if err = syntaxCheckModule(lines, n); err != nil { + return err + } + *n++ + l, err = PrepareLine(lines[*n]) + continue + } + + // Die Zeile enthält einen Parameter + if IsParameter(n) { + if err = validateParameter(lines, n); err != nil { + return err + } + *n++ + + // Multiline-Parameter können die Zeilennummer manipulieren + if LinesFinished(n) { + err = GenerateSyntaxError(static.MODULE_MISSING_CLOSING_BRACKET, *n-1, lines[len(lines)-1], "") + break + } + + // Die nächste Zeile vorbereiten, dann an den Anfang der Schleife gehen + l, err = PrepareLine(lines[*n]) + continue + } else + + // Die Zeile enthält eine Variable + if IsVariableAssignment(n) { + if err = validateVariableAssignment(lines, n); err != nil { + return err + } + *n++ + l, err = PrepareLine(lines[*n]) + continue + } + + // Ungültiger Ausdruck + return GenerateSyntaxError(static.MODULE_INVALID_EXPRESSION, *n, l, "") + } + + return err +} + +// Diese Methode validiert den Syntax einer Variablen-Zuweisung. +func validateVariableAssignment(lines []string, n *int) (err error) { + line, err := RemoveInlineComment(lines[*n]) + if err != nil { + return err + } + + // Name und Wert der Variablenzuweisung trennen + name, value := SplitVariable(line) + + // Name der Variable validieren + if err = validateVariableName(name); err != nil { + return GenerateSyntaxError(err.Error(), *n, lines[*n], name) + } + + // ist Wert der Variable ein Wert? + if IsValue(value) { + if err = validateVariableValue(value); err != nil { + return GenerateSyntaxError(err.Error(), *n, lines[*n], value) + } + } else + + // ist Wert der Variable eine weitere Variable? + if IsVariable(value) { + if err = validateVariableName(name); err != nil { + return GenerateSyntaxError(err.Error(), *n, lines[*n], value) + } + } else { + // Weder Wert noch Variable, also Error + return GenerateSyntaxError(static.VARIABLE_INVALID_VALUE, *n, lines[*n], value) + } + + return nil +} + +// Diese Methode validiert den Syntax eines Parameters. +func validateParameter(lines []string, n *int) (err error) { + line, err := PrepareLine(lines[*n]) + if err != nil { + return err + } + + name, value := SplitParameter(line) + + // Parametername validieren + if err = validateParameterName(name); err != nil { + return GenerateSyntaxError(err.Error(), *n, lines[*n], name) + } + + // Wert validieren + if lineNo, err := validateParameterValue(value, lines, n); err != nil { + return GenerateSyntaxError(err.Error(), lineNo, lines[lineNo], value) + } + + return nil +} + +// Diese Methode validiert den Syntax eines Variablennames. +func validateVariableName(name string) error { + // Variablenname checken + if !(s.HasPrefix(name, "%") && s.HasSuffix(name, "%")) { + return errors.New(static.VARIABLE_INVALID_NAME + static.VARIABLE_MISSING_PERCENTAGE) + } + + // Erlaubte Zeichen des Variablennames checken + match, _ := regexp.MatchString(static.REG_VARIABLE_NAME, s.Trim(name, "%")) + if !match { + return errors.New(static.VARIABLE_INVALID_NAME + static.INVALID_CHARACTERS) + } + + return nil +} + +// Diese Methode validiert den Syntax des Werts einer Variable. +func validateVariableValue(_ string) error { + // Aktuell gibt es nichts zu validieren, außer dass ein Wert in Anführungszeichen oder Backticks stehen muss + // Methode ist hier für einheitliche Validierung oder spätere Additionen + return nil +} + +// Diese Methode validiert den Syntax eines Parameter-Namens. +func validateParameterName(in string) error { + // Erlaubte Zeichen im Parametername checken + match, _ := regexp.MatchString(static.REG_MODULE_NAME, in) + if !match { + return errors.New(static.MODULE_INVALID_MODULE_NAME + static.INVALID_CHARACTERS) + } + + return nil +} + +// Diese Methode validiert den Wert eines Parameters. +func validateParameterValue(value string, lines []string, n *int) (int, error) { + var err error + + // Multiline-Parameter validieren + firstLine := *n + if s.HasPrefix(value, "`") { + + // Inline-Kommentar entfernen + value, err = PrepareLine(lines[*n]) + if err != nil { + return 0, err + } + + // Wenn sich in der aktuellen Zeile nur ein einzelnes Backtick befindet, wissen wir, dass der + // Multiline-Parameter über mehrere Zeilen geht. Diese Zeile muss einzeln abgefangen und + // übersprungen werden, da die folgende for-Schleife nicht funktioniert, wenn in der ersten Zeile + // ausschließlich ein Backtick und sonst nichts steht. + if s.Index(value, "`") == s.LastIndex(value, "`") { + *n++ + value, err = PrepareLine(lines[*n]) + if err != nil { + return 0, err + } + } + + // Den Multiline-Parameter iterieren + for ; !s.HasSuffix(value, "`"); { + + // Hinter dem Ende des Multiline-Parameters darf kein Text stehen. + if *n != firstLine && s.Contains(value, "`") { + return firstLine, errors.New(static.MODULE_NO_TEXT_BEHIND_MULTILINE) + } + + // Das Ende der Datei wurde erreicht, ohne dass der Multiline-Parameter geschlossen wurde. + if *n+1 >= len(lines) { + return firstLine, errors.New(static.MODULE_VALUE_INVALID_MULTILINE + static.MISSING_TICKS) + } + *n++ + + // Die nächste Zeile vorbereiten + value, err = PrepareLine(lines[*n]) + if err != nil { + return 0, err + } + } + + return *n, nil + } + + // Standard-Parameter validieren + // Parameter-Value muss entweder eine Condition sein oder mit Anführungszeichen beginnen/aufhören + if !IsCondition(value) { + if !s.HasPrefix(value, "\"") || !s.HasSuffix(value, "\"") { + return *n, errors.New(static.MODULE_VALUE_INVALID + static.INVALID_SYNTAX_OR_MISSING_QUOTATIONMARKS) + } + + // Die Parametervalue darf nicht leer sein + if s.Trim(value, "\"") == "" { + return *n, errors.New(static.PARAMETER_EMPTY) + } + } + + return 0, nil +} diff --git a/auditorium/config/config-interpreter/config_interpreter.go b/auditorium/config/config-interpreter/config_interpreter.go new file mode 100644 index 0000000..91a0d42 --- /dev/null +++ b/auditorium/config/config-interpreter/config_interpreter.go @@ -0,0 +1,171 @@ +// In diesem Package wird die vorher eingelesene Konfiguration interpretiert und validiert. Das bedeutet, dass +// Parameter ohne Programmstart abgearbeitet werden und die angegebenen Pfade validiert werden. +package config_interpreter + +import ( + "fmt" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/permissions" + "github.com/pkg/errors" + "io/fs" + "path/filepath" + "reflect" + s "strings" +) + +// Diese Methode bekommt den Pointer zu einem ConfigStruct übergeben. Sie arbeitet ausstehende Parameter ab +// und validiert den Pfad zu der Audit-Konfiguration, sowie den Output-Pfad. +// Wenn keine Errors auftreten, gibt sie den Boolean continueExecution zurück. Ist dieser false, +// wird das Programm nach dem Ausführen dieser Methode beendet. Das ist beispielsweise dann der Fall, +// wenn nur die Version ausgegeben werden soll. +func InterpretConfig(cs *ConfigStruct) (continueExecution bool, err error) { + Info(SeperateTitle("CLI-Interpreter")) + switch { + case cs.Version: + InfoPrintAlways("Aktuelle Version: " + static.CURRENT_VERSION) + return false, nil + + case cs.CreateDefaultConfig: + if err = writeStructToConfigFile(*cs, "./config.ini"); err != nil { + return false, errors.New("Beim Erstellen der Konfigurations-Datei ist Error aufgetreten: " + err.Error()) + } else { + InfoPrintAlways("Es wurde erfolgreich eine Default-File erstellt.") + return false, nil + } + + case cs.SaveConfiguration: + if err := writeStructToConfigFile(*cs, cs.Config); err != nil { + return false, errors.New("Beim Speichern der Konfigurations-Datei ist ein Error augetreten: " + err.Error()) + } else { + Info("Die Konfiguration wurde erfolgreich überschrieben!") + } + + case cs.VerbosityLog == 0: + Warn("Das Log-Level wurde auf NONE gesetzt. Es werden keine Nachrichten in den Log geschrieben.") + + case cs.VerbosityConsole == 0: + Warn("Das Konsolen-Verbosity-Level wurde auf NONE gesetzt. In der Konsole werden keinerlei Ausgaben gemacht.") + } + + // Pfad der Audit-Datei validieren + err = validateAuditConfigPath(cs) + if err != nil && cs.ShowModule == "" { + return false, err + } else { + err = nil + } + + if cs.Zip && cs.ZipOnly { + return false, errors.New("-zip und -zipOnly widersprechen sich und dürfen nicht gleichzeitig gesetzt werden.") + } + + return true, err +} + +// Diese Methode validiert den Output-Pfad. Dafür wird überprüft ob der Pfad existiert und ob +// der Prozess Lese- und Schreibrechte hat. +func ValidateOutputPath(cs *ConfigStruct) (err error) { + cs.OutputPath, err = util.GetAbsolutePath(cs.OutputPath) + if err != nil { + return err + } + + read, write, isDir, err := permissions.Permission(cs.OutputPath) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return errors.New("Der Output-Pfad existiert nicht oder ist kein Ordner: " + cs.OutputPath) + } + return err + } + + if !(read && write) { + return errors.New("Der Output-Pfad ist vom ausführenden Benutzer nicht les- und/oder schreibbar: " + cs.OutputPath) + } + + if !isDir { + return errors.New("Der Output-Pfad ist kein Ordner: " + cs.OutputPath) + } + + return nil +} + +// Diese Methode validiert den Pfad zur Audit-Konfiguration. Dafür wird überprüft ob der Pfad existiert und ob +// der Prozess Lese- und Schreibrechte hat. Des Weiteren wird überprüft, ob die angegebene Datei vom korrekten Typ ist. +func validateAuditConfigPath(cs *ConfigStruct) (err error) { + // Relativen Pfad in absoluten Pfad konvertieren + cs.AuditConfig, err = util.GetAbsolutePath(cs.AuditConfig) + + if err != nil { + return err + } + read, _, isDir, err := permissions.Permission(cs.AuditConfig) + + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + path, _ := util.GetAbsolutePath(static.EXPECTED_AUDIT_PATH) + if path == cs.AuditConfig { + return errors.New("Es konnte keine Audit-Konfiguration gefunden werden.") + } else { + return errors.New("Es konnte keine Audit-Konfiguration an folgendem Pfad gefunden werden: " + cs.AuditConfig) + } + + } else if errors.Is(err, fs.ErrPermission) { + return errors.New("Der Prozess hat nicht die benötigten Berechtigungen um den Pfad der angegebenen Audit-Konfigurationsdatei zu öffnen. Wurde keine Datei explizit angegeben, hat der Prozess keine Berechtigungen für den Pfad der Executable, an welchem eine Audit-Konfigurationsdatei gesucht wird.") + } else { + return errors.New("Beim Bestimmen der Berechtigungen der Audit-Konfiguration ist ein unbekannter Fehler aufgetreten: " + err.Error()) + } + } + + if isDir { + return errors.New("Die angegebene Audit-Konfiguration ist keine Datei, sondern ein Pfad.") + } + + if !read { + return errors.New("Der ausführende Benutzer hat keine Lese-Rechte für die angegebene Audit-Konfiguration.") + } + + // Dateiendung der Audit-Datei + if cs.AuditConfig[s.LastIndex(cs.AuditConfig, ".")+1:] != "jba" { + return errors.New("Die Audit-Config Datei ist nicht vom Format \".jba.\"") + } + + Debug("Der Pfad zur Audit-Konfiguration ist valide.") + return nil +} + +// Diese Methode schreibt ein ConfigStruct in die Datei am übergebenen Pfad. Es werden nur Parameter in die Datei +// geschrieben, die keinen leeren Wert haben. +func writeStructToConfigFile(cs ConfigStruct, path string) error { + r, w, isDir, err := permissions.Permission(".") + if err != nil { + return err + } + + if r && w && isDir { + var defaultConfig []string + defaultConfig = append(defaultConfig, "[ENVIRONMENT]") + + val := reflect.ValueOf(&cs).Elem() + for i := 0; i < val.NumField(); i++ { + if val.Field(i).Interface() != "" && s.ToLower(val.Type().Field(i).Name) != "createdefaultconfig" && s.ToLower(val.Type().Field(i).Name) != "config" && s.ToLower(val.Type().Field(i).Name) != "saveconfiguration" { + if s.ToLower(val.Type().Field(i).Name) == "outputpath" { + defaultConfig = append(defaultConfig, fmt.Sprint(val.Type().Field(i).Name)+"="+filepath.Dir(fmt.Sprint(val.Field(i).Interface()))) + } else { + defaultConfig = append(defaultConfig, fmt.Sprintf("%v=%v", val.Type().Field(i).Name, val.Field(i).Interface())) + } + } + } + path, err = util.GetAbsolutePath(path) + if err != nil { + return err + } + + if err = util.CreateFile(defaultConfig, path); err != nil { + return err + } + } + return nil +} diff --git a/auditorium/config/config-parser/config_parser.go b/auditorium/config/config-parser/config_parser.go new file mode 100644 index 0000000..b319f94 --- /dev/null +++ b/auditorium/config/config-parser/config_parser.go @@ -0,0 +1,438 @@ +// Dieses Package verwaltet die Commandline-Flags, sucht und liest eine Konfigurations-Datei ein und liefert ein Objekt +// vom Typ models.ConfigStruct mit allen gesetzten Werten zurück. Eine Besonderheit des Packages ist, dass es anders +// als alle anderen JBA-Packages nicht den Logger verwendet sondern stattdessen ein Slice aus models.LogMsg-Objekten +// zurückgibt. Dies liegt daran, dass der von anderen Packages verwendete Logger erst nach dem Parsen der Konfiguration +// initialisiert werden kann, da hier beispielsweise das Log-Level gesetzt wird. Daher werden Log-Einträge "gesammelt" +// und bei der Initialisierung des Loggers sozusagen nachträglich in den Log geschrieben. +package config_parser + +import ( + "flag" + "fmt" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "github.com/pkg/errors" + "io/ioutil" + "os" + "strconv" + s "strings" +) + +var ( + tempLogger []LogMsg + flags []cliFlag +) + +// Diese Methode ist die erste Methode, die im Jungbusch-Auditorium aufgerufen wird. Sie erzeugt ein +// models.ConfigStruct-Objekt mit den in static definierten Default-Werten. Ein Pointer zu diesem Objekt wird dann an +// die configHandler-Methode übergeben, welche die gesetzten Werte mit denen aus der config.ini-Datei überschreibt. +// Daraufhin wird die commandlineHandler-Methode mit demselben Pointer aufgerufen, welche alle auf der Commandline +// angegebenen Parameter parsed und in das Struct setzt. +func LoadConfig() (ConfigStruct, []LogMsg) { + tempLogger = make([]LogMsg, 0) + add(LogMsg{Message: "Das Jungbusch-Auditorium wurde gestartet.", Level: 3}) + add(LogMsg{Message: logger.SeperateTitle("Commandline-Parser"), Level: 3}) + initializeFlag() + + cs := ConfigStruct{ + AuditConfig: static.EXPECTED_AUDIT_PATH, + OutputPath: static.DEFAULT_OUTPUT_PATH, + VerbosityLog: static.DEFAULT_LOG_VERBOSITY, + VerbosityConsole: static.DEFAULT_CONSOLE_VERBOSITY, + Config: config_Flag, + } + + // Zuerst wird die Config-Datei gesucht, eingelesen und in das Struct gesetzt (wenn vorhanden) + err := configHandler(&cs) + if err != nil { + add(LogMsg{Message: err.Error(), Level: 1}) + } + + // Anschließend werden die Commandline-Parameter geparsed. + // Sie überschreiben dabei die Werte, welche die Config-Datei gesetzt hat + err = commandlineHandler(&cs) + if err != nil { + add(LogMsg{Message: err.Error(), Level: 1}) + } + + return cs, tempLogger +} + +// Diese Funktion nimmt ein ConfigStruct entgegen, welches bisher nur mit Default-Werten gefüllt ist. +// Es wird eine Konfigurations-Datei gesucht und eingelesen. +// Für eine Beschreibung, welche Konfigurations-Datei eingelesen wird, siehe Handbuch +func configHandler(cs *ConfigStruct) (err error) { + // Nach einer JBA suchen, wenn genau eine vorhanden ist die auch setzten + if err = findAuditConfigs(cs); err != nil { + // Wir müssen hier keinen Error schmeißen, da sonst das Programm unnötig beendet wird, wir geben stattdessen nur eine Warnung aus + add(LogMsg{Message: "Unbekannter Fehler beim Suchen von .jba-Dateien im Pfad der Executable: " + err.Error(), Level: 2}) + } + + if cs.Config != "" { + cs.Config, err = util.GetAbsolutePath(cs.Config) + if err != nil { + return + } + + if !s.HasSuffix(cs.Config, ".ini") { + return errors.New("Die Konfigurations-Datei muss die Dateiendung .ini haben.") + } + + // Wenn explizit eine Config-File angegeben wurde, diese aber nicht existiert beenden wir das Programm mit einem error + add(LogMsg{Message: "Die Konfigurationsdatei am Pfad \"" + cs.Config + "\" wird verwendet.", Level: 4}) + err = readConfigFile(cs) + if err != nil { + return err + } + } else { + if cs.Config == "" { + // Es wurde keine Config angegeben, wir setzen den Pfad auf den Default-Wert + cs.Config = static.EXPECTED_CONFIG_PATH + } + + cs.Config, err = util.GetAbsolutePath(cs.Config) + if err != nil { + return + } + + err = readConfigFile(cs) + + // Falls die Datei nicht gefunden wurde, ignorieren wir den Error und machen erstmal mit den Default-Werten weiter + if err != nil { + if s.Contains(err.Error(), "existiert nicht") { + add(LogMsg{Message: "Es wurde keine Konfigurations-Datei gefunden oder angegeben. Die Default-Werte werden verwendet.", Level: 2}) + } else { + add(LogMsg{Message: "Fehler in der config.ini: " + err.Error(), Level: 1}) + return + } + err = nil + } else { + add(LogMsg{Message: "Die Konfigurationsdatei am Pfad \"" + static.EXPECTED_CONFIG_PATH + "\" wird eingelesen.", Level: 4}) + } + } + return err +} + +// Diese Funktion nimmt ein ConfigStruct entgegen, welches mit Werten aus der Konfigurations-Datei +// und/oder Default-Werten gefüllt ist. +// Diese Werte werden nun von den per Commandline angegebenen Parametern überschrieben, wenn vorhanden. +func commandlineHandler(cs *ConfigStruct) (err error) { + if flag.NFlag() > 0 { + add(LogMsg{Message: "Es wurden " + strconv.Itoa(flag.NFlag()) + " Commandline-Parameter angegeben.", Level: 4}) + err = setCliParameters(cs) + if err != nil { + return err + } + } else { + add(LogMsg{Message: "Es wurden keine Commandline-Parameter angegeben.", Level: 4}) + } + return err +} + +// In dieser Funktion werden die Werte aller Commandline-Parameter ausgelesen, ggf. in den korrekten Datentyp +// konvertiert und in das Config-Struct gesetzt +func setCliParameters(cs *ConfigStruct) (err error) { + if auditConfig_Flag != "" { + cs.AuditConfig = auditConfig_Flag + } + + if outputPath_Flag != "" { + cs.OutputPath = outputPath_Flag + } + + if verbosityLog_Flag != -1 || argsContainFlag("verbosityLog") || argsContainFlag("vl") { + cs.VerbosityLog = verbosityLog_Flag + } + + if verbosityConsole_Flag != -1 || argsContainFlag("verbosityConsole") || argsContainFlag("vc") { + cs.VerbosityConsole = verbosityConsole_Flag + } + + fmt.Println(os.Args) + if skipModuleCompatibilityCheck_Flag || argsContainFlag("skipModuleCompatibilityCheck") { + fmt.Println(skipModuleCompatibilityCheck_Flag) + cs.SkipModuleCompatibilityCheck = skipModuleCompatibilityCheck_Flag + } + + if keepConsoleOpen_Flag || argsContainFlag("keepConsoleOpen") { + cs.KeepConsoleOpen = keepConsoleOpen_Flag + } + + if forceOS_Flag != "" { + cs.ForceOS = forceOS_Flag + } + + if ignoreMissingPrivileges_Flag || argsContainFlag("ignoreMissingPrivileges") { + cs.IgnoreMissingPrivileges = ignoreMissingPrivileges_Flag + } + + if zip_Flag || argsContainFlag("zip") { + cs.Zip = zip_Flag + } + + if zipOnly_Flag || argsContainFlag("zipOnly") { + cs.ZipOnly = zipOnly_Flag + } + + if version_Flag || argsContainFlag("version") { + cs.Version = version_Flag + } + + if createDefaultConfig_Flag || argsContainFlag("createDefault") { + cs.CreateDefaultConfig = createDefaultConfig_Flag + } + + if showModules_Flag || argsContainFlag("showModules") { + cs.ShowModule = "all" + } + + if showModuleInfo_Flag != "" || argsContainFlag("showModuleInfo") { + cs.ShowModule = showModuleInfo_Flag + } + + if checkConfiguration_Flag || argsContainFlag("checkConfiguration") { + cs.CheckConfiguration = checkConfiguration_Flag + } + + if checkSyntax_Flag || argsContainFlag("checkSyntax") || argsContainFlag("syntax") { + cs.CheckSyntax = checkSyntax_Flag + } + + if alwaysPrintProgress_Flag || argsContainFlag("alwaysPrintProgress") { + cs.AlwaysPrintProgress = alwaysPrintProgress_Flag + } + + if saveConfiguration_Flag || argsContainFlag("saveConfiguration") || argsContainFlag("s") { + cs.SaveConfiguration = saveConfiguration_Flag + } + + return err +} + +func argsContainFlag(in string) bool { + for _, arg := range os.Args { + if s.Contains(arg, "--") || s.Contains(arg, "-") { + if s.HasPrefix(s.ToLower(s.Trim(arg, "-")), s.ToLower(in)) { + return true + } + } + } + return false +} + +// Diese Funktion findet am Pfad der Executable alle Dateien, welche die Dateiendung .jba haben. +// Wenn genau eine jba-Datei gefunden und keine andere explizit angegeben ist, +// wird diese automatisch vom Programm verwendet. +func findAuditConfigs(cs *ConfigStruct) error { + var files []string + + // Alle Dateinamen aus dem Verzeichnis holen + path, err := util.GetAbsolutePath("./") + if err != nil { + return err + } + fileInfo, err := ioutil.ReadDir(path) + if err != nil { + return err + } + + // Nach .jba Dateien suchen + for _, file := range fileInfo { + if file.Name()[s.LastIndex(file.Name(), ".")+1:] == "jba" { + files = append(files, file.Name()) + } + } + + // Wenn genau eine Datei im Pfad vorhanden ist, verwenden wir diese + if len(files) == 1 { + cs.AuditConfig, err = util.GetAbsolutePath(files[0]) + if err != nil { + return err + } + } + return nil +} + +// Liest die übergebene Konfigurations-Datei vom System ein. Dabei werden Fehler erzeugt, wenn Key oder Wert fehlen, +// oder wenn ein ungültiger Key angegeben wurde. Alle Werte werden in das übergebene ConfigStruct geschrieben. +func readConfigFile(cs *ConfigStruct) (err error) { + // Config Datei laden + configSlice, err := util.ReadFile(cs.Config) + if err != nil { + return errors.New("Unbekannter Fehler beim Einlesen der Konfigurations-Datei: " + err.Error()) + } + + // Durch die eingelesene Datei iterieren und die Parameter setzen + for _, line := range configSlice { + if s.TrimSpace(line) != "" { + if s.Contains(line, "=") { + line = s.Replace(line, "\"", "", -1) + values := s.SplitN(line, "=", 2) + + values[0] = s.Replace(values[0], " ", "", -1) + values[1] = s.TrimSpace(values[1]) + + if values[1] == "" { + return errors.New("Der Wert eines Keys darf nicht leer sein.") + } + + switch s.ToLower(values[0]) { + + case "auditconfig": + cs.AuditConfig = values[1] + + case "config": + // nichts + + case "outputpath": + cs.OutputPath = values[1] + + case "verbositylog": + verbosityLog, err := getLogLevelFromString(values[1]) + if err != nil { + return err + } + cs.VerbosityLog = verbosityLog + + case "verbosityconsole": + verbosityLog, err := getLogLevelFromString(values[1]) + if err != nil { + return err + } + cs.VerbosityConsole = verbosityLog + + case "skipmodulecompatibilitycheck": + if err = parseAndSetBool(&cs.SkipModuleCompatibilityCheck, values[1]); err != nil { + return err + } + + case "keepconsoleopen": + if err = parseAndSetBool(&cs.KeepConsoleOpen, values[1]); err != nil { + return err + } + + case "forceOS": + cs.ForceOS = values[1] + + case "ignoremissingprivileges": + if err = parseAndSetBool(&cs.IgnoreMissingPrivileges, values[1]); err != nil { + return err + } + + case "zip": + if err = parseAndSetBool(&cs.Zip, values[1]); err != nil { + return err + } + + case "ziponly": + if err = parseAndSetBool(&cs.ZipOnly, values[1]); err != nil { + return err + } + + case "version": + if err = parseAndSetBool(&cs.Version, values[1]); err != nil { + return err + } + + case "showmodules": + if rslt, err := util.ParseStringToBool(values[1]); err == nil && rslt { + cs.ShowModule = "all" + } else { + return err + } + + case "showmoduleinfo": + if values[1] != "" { + cs.ShowModule = showModuleInfo_Flag + } + + case "checkconfiguration": + if err = parseAndSetBool(&cs.CheckConfiguration, values[1]); err != nil { + return err + } + + case "checksyntax": + if err = parseAndSetBool(&cs.CheckSyntax, values[1]); err != nil { + return err + } + + case "alwaysprintprogress": + if err = parseAndSetBool(&cs.AlwaysPrintProgress, values[1]); err != nil { + return err + } + + case "saveconfiguration": + if err = parseAndSetBool(&cs.SaveConfiguration, values[1]); err != nil { + return err + } + + case "createdefaultconfig": + if err = parseAndSetBool(&cs.CreateDefaultConfig, values[1]); err != nil { + return err + } + + default: + return errors.New("Ungültiger Key: " + values[0]) + } + } else { + if !(s.HasPrefix(s.TrimSpace(line), "[") && s.HasSuffix(s.TrimSpace(line), "]")) { + return errors.New("Ungültiger Wert (fehlender Key): " + line) + } + } + } + } + + return nil +} + +// Parsed den Wert eines übergebenen Strings in einen Boolean und setzt das Ergebnis +// in den übergebenen boolean-Pointer. Verwendet parseStringToBool. +func parseAndSetBool(toSet *bool, value string) (err error) { + var rslt bool + if rslt, err = util.ParseStringToBool(value); err == nil { + *toSet = rslt + } + return err +} + +// Konvertiert das übergebene Loglevel (String) in einen int +func getLogLevelFromString(in string) (int, error) { + out, err := strconv.Atoi(in) + if err != nil { + return 0, errors.New("Das angegebene Loglevel ist keine ganze Zahl.") + } else { + return out, nil + } +} + +// Wrapper für Slice-Append +func add(msg LogMsg) { + tempLogger = append(tempLogger, msg) +} + +// Wird aufgrund eines Golang-Quirks in den Tests benötigt +func ResetFlags() { + auditConfig_Flag = "" + config_Flag = "" + outputPath_Flag = "" + + verbosityLog_Flag = -1 + verbosityConsole_Flag = -1 + skipModuleCompatibilityCheck_Flag = false + keepConsoleOpen_Flag = false + forceOS_Flag = "" + ignoreMissingPrivileges_Flag = false + alwaysPrintProgress_Flag = false + version_Flag = false + createDefaultConfig_Flag = false + showModules_Flag = false + showModuleInfo_Flag = "" + checkConfiguration_Flag = false + checkSyntax_Flag = false + zipOnly_Flag = false + zip_Flag = false + + saveConfiguration_Flag = false +} diff --git a/auditorium/config/config-parser/config_parser_flags.go b/auditorium/config/config-parser/config_parser_flags.go new file mode 100644 index 0000000..a0645ad --- /dev/null +++ b/auditorium/config/config-parser/config_parser_flags.go @@ -0,0 +1,291 @@ +package config_parser + +import ( + "flag" + "fmt" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + s "strings" +) + +// In diesem Struct werden die Beschreibungen für die Commandline-Flags gespeichert, die für das Anzeigen der +// Usage-Page benötigt werden +type cliFlag struct { + name string + desc string + datatype string +} + +// Beschreibung der einzelnen Parameter: Siehe models.ConfigStruct +var ( + // Programm-Parameter + auditConfig_Flag string + config_Flag string + outputPath_Flag string + verbosityLog_Flag int + verbosityConsole_Flag int + skipModuleCompatibilityCheck_Flag bool + keepConsoleOpen_Flag bool + forceOS_Flag string + ignoreMissingPrivileges_Flag bool + zip_Flag bool + zipOnly_Flag bool + + // One-And-Done-Parameter (Programm führt keine Audits aus) + version_Flag bool + createDefaultConfig_Flag bool + showModules_Flag bool // Alle Module (bool) + showModuleInfo_Flag string // Ein einzelnes Modul (string) + checkConfiguration_Flag bool + checkSyntax_Flag bool + alwaysPrintProgress_Flag bool + + // Misc + saveConfiguration_Flag bool // Speichern der Commandline-Parameter in Config +) + +// Diese Methode initialisiert die Flags der Commandline, enthält die Beschreibungen der Flags, sowie die Help-Page +func initializeFlag() { + if len(flags) == 0 { + flag.StringVar(&auditConfig_Flag, "auditConfig", "", "") + flag.StringVar(&auditConfig_Flag, "a", "", "") + + flag.StringVar(&config_Flag, "config", "", "") + flag.StringVar(&config_Flag, "c", "", "") + + flag.StringVar(&outputPath_Flag, "outputPath", "", "") + flag.StringVar(&outputPath_Flag, "o", "", "") + + flag.IntVar(&verbosityLog_Flag, "verbosityLog", -1, "") + flag.IntVar(&verbosityLog_Flag, "vl", -1, "") + + flag.IntVar(&verbosityConsole_Flag, "verbosityConsole", -1, "") + flag.IntVar(&verbosityConsole_Flag, "vc", -1, "") + + flag.BoolVar(&skipModuleCompatibilityCheck_Flag, "skipModuleCompatibilityCheck", false, "") + + flag.BoolVar(&keepConsoleOpen_Flag, "keepConsoleOpen", false, "") + flag.StringVar(&forceOS_Flag, "forceOS", "", "") + flag.BoolVar(&ignoreMissingPrivileges_Flag, "ignoreMissingPrivileges", false, "") + flag.BoolVar(&zip_Flag, "zip", false, "") + flag.BoolVar(&zipOnly_Flag, "zipOnly", false, "") + + flag.BoolVar(&version_Flag, "version", false, "") + flag.BoolVar(&createDefaultConfig_Flag, "createDefault", false, "") + flag.BoolVar(&showModules_Flag, "showModules", false, "") + flag.StringVar(&showModuleInfo_Flag, "showModuleInfo", "", "") + flag.BoolVar(&checkConfiguration_Flag, "checkConfiguration", false, "") + flag.BoolVar(&checkSyntax_Flag, "checkSyntax", false, "") + flag.BoolVar(&checkSyntax_Flag, "syntax", false, "") + flag.BoolVar(&alwaysPrintProgress_Flag, "alwaysPrintProgress", false, "") + + flag.BoolVar(&saveConfiguration_Flag, "saveConfiguration", false, "") + flag.BoolVar(&saveConfiguration_Flag, "s", false, "") + + // Initialisieren aller Flags + flags = []cliFlag{ + // + // Version + // + { + name: "-version", + desc: "Version & Datum der letzten Änderung", + datatype: "-", + }, + + // + // Help + // + { + name: "-help", + desc: "Hilfe-Seite", + datatype: "-", + }, + + // + // ShowModules + // + { + name: "-showModules", + desc: "Liste mit allen Modulnamen", + datatype: "-", + }, + + // + // Show specific module + // + { + name: "-showModuleInfo", + desc: "Gibt verfügbare Informationen zu dem Modul aus", + datatype: "", + }, + + // + // JustCheckConfiguration + // + { + name: "-checkConfiguration", + desc: "Überprüft Syntax und Werte der Audit-Konfigurationsdatei", + datatype: "-", + }, + + // + // syntaxCheck + // + { + name: "-checkSyntax, -syntax", + desc: "Überprüft nur die Syntax der Audit-Konfigurationsdatei", + datatype: "-", + }, + + // + // Create Default-Flag + // + { + name: "-createDefault", + desc: "Erzeugt config.ini-Datei mit Default-Werten am Pfad der Executable", + datatype: "-", + }, + + // + // Audit-Config + // + { + name: "-auditConfig, -a", + desc: "Pfad zur Audit-Konfigurations-Datei", + datatype: "", + }, + + // + // Config + // + { + name: "-config, -c", + desc: "Pfad zur Konfigurations-Datei", + datatype: "", + }, + + // + // Output-Path + // + { + name: "-outputPath, -o", + desc: "Pfad zur Output-Directory", + datatype: "", + }, + + // + // Log-Verbosity + // + { + name: "-verbosityLog, -vl", + desc: "Menge der Log-Einträge", + datatype: "<0-4>", + }, + + // + // Console-Verbosity + // + { + name: "-verbosityConsole, -vc", + desc: "Menge der Konsolen-Einträge", + datatype: "<0-4>", + }, + + // + // SkipModuleCompatibility + // + { + name: "-skipModuleCompatibilityCheck", + desc: "Überspringen der internen Modul-Kompatibilitätsüberprüfung", + datatype: "-", + }, + + // + // KeepConsoleOpen + // + { + name: "-keepConsoleOpen", + desc: "Verhindert das sofortige schließen der Konsole nach dem Ausführen des Programms per Doppelclick", + datatype: "-", + }, + + // + // OS + // + { + name: "-forceOS", + desc: "Überschreibt das Ergebnis des OS-Detectors", + datatype: "", + }, + + // + // IgnoreMissingPrivileges + // + { + name: "-ignoreMissingPrivileges", + desc: "Das JBA wird trotz fehlenden Privilegien soweit möglich ausgeführt und nicht frühzeitig beendet", + datatype: "-", + }, + + // + // zip + // + { + name: "-zip", + desc: "Erstellt eine Zip-Datei des Output-Ordners", + datatype: "-", + }, + + // + // zipOnly + // + { + name: "-zipOnly", + desc: "Erstellt eine Zip-Datei des Output-Ordners und löscht den Output-Ordner", + datatype: "-", + }, + + // + // AlwaysPrintProgress + // + { + name: "-alwaysPrintProgress", + desc: "Der Fortschritt der Audit-Schritte wird unabhängig vom Log-Level ausgegeben", + datatype: "-", + }, + + // + // Save cli-params to config + // + { + name: "-saveConfiguration, -s", + desc: "Mit diesem Befehl werden die aktuellen Commandline-Parameter in die config.ini-Datei geschrieben", + datatype: "-", + }, + } + add(LogMsg{Message: "Die Commandline-Flags wurden initialisiert.", Level: 4}) + } + + // Was beim Ausführen von -help oder der inkorrekten Nutzung von Flags ausgegeben wird + flag.Usage = func() { + // Ausgabe der Usage-Page + fmt.Println("Usage:") + fmt.Println("./jungbusch-auditorium | jungbusch-auditorium.exe [NonExec] [Exec] ") + fmt.Println("\nCommandline-Parameter bei denen keine Audit-Schritte ausgeführt werden:") + for n := 0; n < 6; n++ { + fmt.Println(fmt.Sprintf("%-30v", flags[n].name) + fmt.Sprintf("%-17v", flags[n].datatype) + flags[n].desc) + } + fmt.Println("\nCommandline-Parameter bei denen Audit-Schritte ausgeführt werden:") + for n := 6; n < len(flags); n++ { + fmt.Println(fmt.Sprintf("%-35v", flags[n].name) + fmt.Sprintf("%-17v", flags[n].datatype) + flags[n].desc) + } + fmt.Println("\nWeitere Informationen finden Sie im Benutzerhandbuch.") + } + + flag.Parse() + auditConfig_Flag = s.TrimSpace(s.Trim(auditConfig_Flag, "\"")) + config_Flag = s.TrimSpace(s.Trim(config_Flag, "\"")) + outputPath_Flag = s.TrimSpace(s.Trim(outputPath_Flag, "\"")) + forceOS_Flag = s.TrimSpace(s.Trim(forceOS_Flag, "\"")) + showModuleInfo_Flag = s.TrimSpace(s.Trim(showModuleInfo_Flag, "\"")) +} diff --git a/auditorium/modulecontroller/module_controller.go b/auditorium/modulecontroller/module_controller.go new file mode 100644 index 0000000..52f4f99 --- /dev/null +++ b/auditorium/modulecontroller/module_controller.go @@ -0,0 +1,347 @@ +// Dieses Package übernimmt die Kommunikation zwischen dem Rest des Frameworks und der Module. +// Das Framework ist von den Modulen vollständig getrennt, insofern dass die Methoden der Module nie explizit aus dem +// Code aufgerufen wird. Die einzige Referenz auf die Module befinden sich in der Slice static.Modules. +// Stattdessen wird Reflection verwendet um die Methoden der Module aufzurufen. Dafür ist es nötig, dass alle +// Modul-Methoden einheitliche Namen haben. Welche Methodennamen erwartet werden, wird von +// static.MODULE_INITIALIZER_SUFFIX, static.MODULE_EXECUTOR_SUFFIX und static.MODULE_VALIDATOR_SUFFIX. +// Die Methodennamen setzen sich durch den Modulname und den Suffix zusammen. +package modulecontroller + +import ( + "errors" + "fmt" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/acutil" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/modules" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "reflect" + "regexp" + s "strings" +) + +var ( + loadedModules []ModuleSyntax // Speichert alle geladenen Module +) + +// Initialisiert alle Module und überprüft währenddessen, ob die Module mit dem aktuellen OS kompatibel sind, +// und alle benötigten Methoden implementieren. Wenn ja, wird der Syntax des Moduls (models.ModuleSyntax) +// in die Slice loadedModules geschrieben. Mit `skipCompatibilityCheck` kann die Kompatibilitätsüberprüfung +// übersprungen werden. +func Initialize(skipCompatibilityCheck bool) ([]ModuleSyntax, error) { + // Hier basierend auf dem os die ModuleSyntax laden + loadedModules = make([]ModuleSyntax, 0) + Info(SeperateTitle("Module-Intializer")) + + // Durch alle Module iterieren + for n := range static.Modules { + initExists := doesMethodExist(static.Modules[n] + static.MODULE_INITIALIZER_SUFFIX) + executeExists := doesMethodExist(static.Modules[n] + static.MODULE_EXECUTOR_SUFFIX) + + switch { + // Wenn nur eine der Methoden existiert, ist das Modul ungültig. + // Wenn keine existiert, kann es sein dass das Modul aufgrund von GO-Buildtags nicht verfügbar ist, + // daher wird dafür kein Error geworfen + case (initExists && !executeExists) || (!initExists && executeExists): + return []ModuleSyntax{}, errors.New("Dem Modul " + static.Modules[n] + " fehlt mindestens eine der benötigten Methoden. Es benötigt mindestens folgende Methoden: " + static.Modules[n] + static.MODULE_INITIALIZER_SUFFIX + ", " + static.Modules[n] + static.MODULE_EXECUTOR_SUFFIX) + + case initExists && executeExists: + currModule := parseReflectToModuleSyntax(callFunctionFromString(static.Modules[n]+static.MODULE_INITIALIZER_SUFFIX, []reflect.Value{})) + if currModule.ModuleName != static.Modules[n] { + return nil, errors.New("Der in der Initialize-Methode eingetragene Modulname (\"" + currModule.ModuleName + "\") stimmt nicht mit dem Methodenname und dem in static.Modules eingetragenem Wert (\"" + static.Modules[n] + "\") überein.") + } + + // Überprüfen, ob das Modul mit dem aktuellen Betriebssystem kompatibel ist. + markedAsCompatible := util.ArrayContainsString(currModule.ModuleCompatibility, static.OperatingSystem) + wildcardCompatibility := wildcardCompatibilityCheck(currModule.ModuleCompatibility) + if skipCompatibilityCheck || markedAsCompatible || wildcardCompatibility { + + // Wenn es nicht kompatibel ist, aber trotzdem geladen werden soll, geben wir eine Warnung aus. + if (!markedAsCompatible || !wildcardCompatibility) && skipCompatibilityCheck { + Warn("Das Modul " + static.Modules[n] + " wurde nicht als kompatibel mit " + static.OperatingSystem + " markiert. Es wurde geladen, weil der Module-Kompatibilitäts-Check über einen Parameter deaktiviert wurde.") + } else { + Debug("Das Modul " + static.Modules[n] + " wurde erfolgreich geladen.") + } + + // In die zurückzugebende Liste aller geladenen Module einfügen + loadedModules = append(loadedModules, currModule) + } + } + } + + return loadedModules, sanityCheck(loadedModules) +} + +// Überprüft für alle übergeben models.AuditModule-Objekte ob das referenzierte Modul eine Validate-Methode hat. +// Wenn ja, wird diese mit den in der AuditKonfiguration angegebenen Parametern aufgerufen. +func ValidateAuditModules(mod []AuditModule) error { + var err error + for n := range mod { + err = validateModule(mod[n]) + if err != nil { + return err + } + } + return nil +} + +// Wird zum Aufrufen der Hauptmethode (Execute) eines Moduls verwendet werden. Diese Methode parsed +// die zu übergebenden Werte, sowie die Rückgabewerte in den korrekten Datentyp. +func Call(functionName string, m AuditModule) ModuleResult { + // Das Skript-Modul ist das einzige Modul, welches eine custom Einbindung in den Rest des Frameworks hat. + if s.ToLower(functionName) == "script" { + return parseReflectToModuleResult(callFunctionFromString(functionName, parseVariableMapToReflect(m.ModuleParameters, &m.Variables))) + } + return parseReflectToModuleResult(callFunctionFromString(functionName, parseParameterSliceToReflect(m.ModuleParameters))) +} + +// Wird zum Aufrufen der Validate-Methode eines Moduls verwendet werden. Diese Methode parsed +// die zu übergebenden Werte, sowie die Rückgabewerte in den korrekten Datentyp. +// Ist der zurückgegebene Error nil, sind alle Parameter valide. +func CallValidate(functionName string, params ParameterMap) error { + return parseReflectToError(callFunctionFromString(functionName, parseParameterSliceToReflect(params))) +} + +// Sucht den Modul-Syntax für den Übergebenen Modul-Name oder Modul-Alias und gibt diesen als formatierten String zurück. +func GetModuleSyntax(module string) string { + module = s.ToLower(module) + if module == "all" { + var out string + for n := range loadedModules { + out += getModuleDescription(loadedModules[n]) + } + return "\n" + out + } + + for n := range loadedModules { + if s.ToLower(loadedModules[n].ModuleName) == module || util.ArrayContainsString(loadedModules[n].ModuleAlias, module) { + return "\n" + getModuleDescription(loadedModules[n]) + } + } + return "Modul konnte nicht gefunden werden." +} + +// +// +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// Utility +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// +// + +// Überprüft die Modul-Kompatibilität mit Wildcards gegeben ist +func wildcardCompatibilityCheck(moduleCompatibility []string) bool { + for n := range moduleCompatibility { + switch s.ToLower(moduleCompatibility[n]) { + case "all": + return true + case "windows": + return util.ArrayContainsString(static.Windows, static.OperatingSystem) + case "linux": + return util.ArrayContainsString(static.Linux, static.OperatingSystem) + case "darwin": + return util.ArrayContainsString(static.Darwin, static.OperatingSystem) + } + } + return false +} + +// Überprüft ob das im models.AuditModule-Objekt (also ein Audit-Schritt) aufgerufene Modul die Validate-Methode +// implementiert. Wenn ja, wird sie mit den gegebenen Parametern aufgerufen. +// Des Weiteren werden die Validate-Module von Kindern des übergebenen Audit-Schritts ausgeführt. +// Sind alle Parameter laut den Validate-Methoden valide wird nil zurückgegeben, ansonsten ein Error. +func validateModule(mod AuditModule) error { + var err error + + if doesMethodExist(mod.ModuleName + static.MODULE_VALIDATOR_SUFFIX) { + err = CallValidate(mod.ModuleName+static.MODULE_VALIDATOR_SUFFIX, mod.ModuleParameters) + if err != nil { + return err + } else { + for n := range mod.NestedModules { + err = validateModule(mod.NestedModules[n]) + if err != nil { + return err + } + } + } + } + return nil +} + +// Formatiert den models.ModuleSyntax eines Moduls in einen string. +func getModuleDescription(module ModuleSyntax) string { + var out string + + out = fmt.Sprintf("%-25v", "Modul: ") + "\n" + out += fmt.Sprintf("%-25v", " Name, Alias: ") + module.ModuleName + ", " + util.PrintStrArray(module.ModuleAlias) + "\n" + if s.HasSuffix(out, ", \n") { + out = out[:len(out)-3] + "\n" + } + out += fmt.Sprintf("%-25v", " Beschreibung: ") + fmt.Sprint(module.ModuleDescription) + "\n" + out += fmt.Sprintf("%-25v", " Kompatibilität: ") + util.PrintStrArray(module.ModuleCompatibility) + "\n" + out += fmt.Sprintf("%-25v", " Parameter: ") + "\n" + + for n := range module.InputParams { + out += fmt.Sprintf("%-25v", " Name, Alias: ") + module.InputParams[n].ParamName + ", " + util.PrintStrArray(module.InputParams[n].ParamAlias) + "\n" + if s.HasSuffix(out, ", \n") { + out = out[:len(out)-3] + "\n" + } + out += fmt.Sprintf("%-25v", " Beschreibung: ") + module.InputParams[n].ParamDescription + "\n" + out += fmt.Sprintf("%-25v", " Optional: ") + if module.InputParams[n].IsOptional { + out += "Ja\n\n" + } else { + out += "Nein\n\n" + } + } + return out +} + +// Parsed den Rückgabewert eines Calls zurück in models.ModuleSyntax. +func parseReflectToModuleSyntax(in []reflect.Value) ModuleSyntax { + return in[0].Interface().(ModuleSyntax) +} + +// Parsed den Rückgabewert eines Calls in ein Error-Struct. +func parseReflectToError(in []reflect.Value) error { + if in[0].Interface() != nil { + return in[0].Interface().(error) + + } else { + return nil + } +} + +// Parsed den Rückgabewert eines Calls in ein models.ModuleResult-Struct. +func parseReflectToModuleResult(in []reflect.Value) ModuleResult { + return in[0].Interface().(ModuleResult) +} + +// Parsed eine models.ParameterMap in mit Reflect zu verwendenden Reflect-Values. +func parseParameterSliceToReflect(in ParameterMap) []reflect.Value { + return []reflect.Value{reflect.ValueOf(in)} +} + +// Parsed eine models.ParameterMap, sowie models.VariableMap in mit Reflect zu verwendenden Reflect-Values. +func parseVariableMapToReflect(pm ParameterMap, vm *VariableMap) []reflect.Value { + return []reflect.Value{parseParameterSliceToReflect(pm)[0], reflect.ValueOf(vm)} +} + +// Überprüft ob eine Methode mit dem übergebenen Name in Sichtweite des modules.MethodHandler-Objekts existiert. +func doesMethodExist(function string) bool { + st := reflect.TypeOf((*modules.MethodHandler)(nil)) + _, exists := st.MethodByName(function) + return exists +} + +// Ruft die Methode mit dem übergebenen Name und den übergebenen Übergabewerten auf. Gibt Rückgabewerte in einer Slice +// im Format reflect.Value zurück, die zurück in den richtigen Datentyp gecasted werden muss. +func callFunctionFromString(function string, values []reflect.Value) []reflect.Value { + mh := new(modules.MethodHandler) + method := reflect.ValueOf(mh).MethodByName(function) + response := method.Call(values) + return response +} + +// Diese Methode überprüft, ob sich die in den Initialize-Methoden von Modulen gesetzten Namen und Aliase überschneiden. +// Modulnamen müssen einzigarig sein. Ist das nicht der Fall, wird ein passender Error geworfen. +func sanityCheck(mod []ModuleSyntax) error { + // Modulnamen checken + usedNames := make(map[string]bool, 0) + for _, m := range mod { + if hasDisallowedCharacters(m.ModuleName) { + return errors.New("Der Modul-Name enthält ungültige Zeichen: " + m.ModuleName) + } + + if s.TrimSpace(s.Trim(m.ModuleName, "\t")) == "" { + return errors.New("Der Modul-Name darf nicht ein leerer String oder ein String bestehend aus ausschließlich Leerzeichen sein: " + m.ModuleName) + } + + syn := acutil.GetParameterSyntaxFromKeyword(m.ModuleName) + if syn.ParamName != "" { + return errors.New("Der Modul-Name " + m.ModuleName + " überschneidet sich mit dem Jungbusch-Auditorium-Parameter " + syn.ParamName + " (" + syn.ParamDescription + " ). Bitte den Modulnamen ändern.") + } + + if usedNames[m.ModuleName] { + return errors.New("Das Modul " + m.ModuleName + " kommt mehrmals vor. Modulnamen müssen einzigartig sein und dürfen sich nicht mit den Namen/Aliasen anderer Module überschneiden.") + } else { + usedNames[m.ModuleName] = true + } + + for _, sm := range m.ModuleAlias { + if hasDisallowedCharacters(sm) { + return errors.New("Der Modul-Alias enthält ungültige Zeichen: " + sm) + } + + if s.TrimSpace(s.Trim(sm, "\t")) == "" { + return errors.New("Modul-Aliase dürfen nicht ein leerer String oder ein String bestehend aus ausschließlich Leerzeichen sein: " + sm) + } + + syn = acutil.GetParameterSyntaxFromKeyword(sm) + if syn.ParamName != "" { + return errors.New("Der Modul-Alias " + sm + " des Moduls " + m.ModuleName + " überschneidet sich mit dem Jungbusch-Auditorium-Parameter " + syn.ParamName + " (" + syn.ParamDescription + " ). Bitte den Alias ändern oder entfernen.") + } + + if usedNames[sm] { + return errors.New("Das Modul " + sm + " kommt mehrmals vor. Modulnamen müssen einzigartig sein und dürfen sich nicht mit den Namen/Aliasen anderer Module überschneiden.") + } else { + usedNames[sm] = true + } + } + } + + // Modulparameter checken + for _, m := range mod { + usedNames = make(map[string]bool, 0) + for _, mp := range m.InputParams { + if hasDisallowedCharacters(mp.ParamName) { + return errors.New("Der Parameter-Name enthält ungültige Zeichen: " + mp.ParamName) + } + + if s.TrimSpace(s.Trim(mp.ParamName, "\t")) == "" { + return errors.New("Parameter-Namen dürfen nicht ein leerer String oder ein String bestehend aus ausschließlich Leerzeichen sein: " + mp.ParamName) + } + + syn := acutil.GetParameterSyntaxFromKeyword(mp.ParamName) + if syn.ParamName != "" { + return errors.New("Der Parameter-Name " + mp.ParamName + " des Moduls " + m.ModuleName + " überschneidet sich mit dem Jungbusch-Auditorium-Parameter " + syn.ParamName + " (" + syn.ParamDescription + " ). Bitte den Parametername ändern.") + } + + usedNames[mp.ParamName] = true + for _, mpa := range mp.ParamAlias { + if hasDisallowedCharacters(mpa) { + return errors.New("Der Parameter-Alias enthält ungültige Zeichen: " + mpa) + } + + if s.TrimSpace(s.Trim(mpa, "\t")) == "" { + return errors.New("Parameter-Aliase dürfen nicht ein leerer String oder ein String bestehend aus ausschließlich Leerzeichen sein: " + mpa) + } + + syn = acutil.GetParameterSyntaxFromKeyword(mpa) + if syn.ParamName != "" { + return errors.New("Der Parameter-Alias " + mpa + " des Parameters " + mp.ParamName + " des Moduls " + m.ModuleName + " überschneidet sich mit dem Jungbusch-Auditorium-Parameter " + syn.ParamName + " (" + syn.ParamDescription + " ). Bitte den Parametername ändern.") + } + + if usedNames[mpa] { + return errors.New("Der Parametername " + mpa + " kommt im Modul " + m.ModuleName + " mehrmals vor. Parameternamen/Aliase sollten sich nicht überschneiden.") + } else { + usedNames[mpa] = true + } + } + } + } + + return nil +} + +// True, wenn im übergebenen String für einen Modulname ungültige Zeichen enthalten sind. +func hasDisallowedCharacters(in string) bool { + match, _ := regexp.MatchString(static.REG_MODULE_NAME, in) + return !match +} diff --git a/auditorium/modulecontroller/os_detector.go b/auditorium/modulecontroller/os_detector.go new file mode 100644 index 0000000..19ca875 --- /dev/null +++ b/auditorium/modulecontroller/os_detector.go @@ -0,0 +1,63 @@ +package modulecontroller + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "github.com/pkg/errors" + "runtime" + s "strings" +) + +// Versucht das Betriebssystem und seine Version zu bestimmen. Es wird nicht zwischen den unterschiedlichen +// Ausführungen von einzelnen Windows-Versionen (Home/Pro) etc. unterschieden. +func GetOS() (string, error) { + var os string + var err error + + Info(SeperateTitle("OS-Detector")) + Info("Der OS-Detector wurde gestartet.") + + switch runtime.GOOS { + case "windows": + suffix := []string{"home", "pro", "enterprise", "enterpriseevaluation", "education", "essentials", "essentialsevaluation", "standard", "evaluation", "standardevaluation", "datacenter", "datacenterevaluation", "mobile", "foundation", "rt", "web", "ultimate", "professional", "homepremium", "homebasic", "starter", "business"} + name, err := util.RegQuery(`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion`, "ProductName") + if err != nil { + Err(err.Error()) + return "", err + } + name = s.ToLower(util.CompressString(name)) + Debug("Betriebssystem (roh): " + name) + + os = util.RemoveFromString(name, suffix) + + case "linux": + name, err := util.ExecCommand("sed -n 3p /etc/os-release") + if err != nil { + Err(err.Error()) + return "", err + } + name = s.Trim(name, "\n") + Debug("Betriebssystem (roh): " + name) + os = s.ReplaceAll(name[s.LastIndex(name, "=")+1:], "\"", "") + + case "darwin": + version, err := util.ExecCommand("sw_vers -productVersion") + Debug("Betriebssystem (roh): MACOS " + version) + if err != nil { + Err(err.Error()) + return "", err + } + os = "macos" + version[0:s.LastIndex(version, ".")] + + default: + Debug("Die von GO festgelegte Architektur ist " + runtime.GOOS + ". Unterstützt werden nur \"windows\", \"linux\" und \"darwin\".") + err = errors.New("Das Betriebsystem wird nicht unterstützt.") + } + + os = s.ToLower(os) + if os != "" { + Info("Der OS-Detector wurde mit folgendem Ergebnis beendet: " + os) + } + + return os, err +} diff --git a/auditorium/outputgenerator/output_generator.go b/auditorium/outputgenerator/output_generator.go new file mode 100644 index 0000000..d78a777 --- /dev/null +++ b/auditorium/outputgenerator/output_generator.go @@ -0,0 +1,255 @@ +package outputgenerator + +import ( + "archive/zip" + "bytes" + "encoding/json" + "fmt" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "io" + "io/fs" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +type ReportEntry struct { + ID string `json:"id"` + Desc string `json:"desc"` + Print string `json:"print,omitempty"` + Artifacts []models.Artifact `json:"artifacts"` + Expected string `json:"expected,omitempty"` + Actual string `json:"actual,omitempty"` + Error string `json:"error,omitempty"` + Result string `json:"result"` + Nested []ReportEntry `json:"nested,omitempty"` +} + +type Metadata struct { + Os string `json:"os"` + Root bool `json:"root"` + Total int `json:"total"` + Passed int `json:"passed"` + Notpassed int `json:"not_passed"` + Unsuccessful int `json:"unsuccessful"` + Notexecuted int `json:"not_executed"` + Started string `json:"started"` + Elapsed string `json:"elapsed"` + Report []ReportEntry `json:"report"` +} + +type stats struct { + total int + passed int + notpassed int + unsuccessful int + notexecuted int +} + +// GenerateOutput erstellt den Report und speichert die Artefakte am spezifizierten Pfad. +func GenerateOutput(report []ReportEntry, outputPath string, numberOfModules int, zip bool, start time.Time, elapsed time.Duration) error { + Info(SeperateTitle("Output-Generator")) + + if err := saveArtifacts(report, outputPath+static.PATH_SEPERATOR+"artifacts"); err != nil { + return err + } + if err := generateReport(report, outputPath, numberOfModules, start, elapsed); err != nil { + return err + } + + if zip { + if err := zipFolder(outputPath); err != nil { + return err + } + } + + return nil +} + +func generateReport(report []ReportEntry, outputPath string, numberOfModules int, start time.Time, elapsed time.Duration) (err error) { + s := getStats(report, numberOfModules) + + meta := Metadata{ + Os: static.OperatingSystem, + Root: static.HasElevatedPrivileges, + Total: s.total, + Passed: s.passed, + Notpassed: s.notpassed, + Unsuccessful: s.unsuccessful, + Notexecuted: s.notexecuted, + Started: start.Format(time.RFC1123), + Elapsed: elapsed.String(), + Report: cleanArtifacts(report), + } + + buf := new(bytes.Buffer) + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + enc.SetIndent("", "\t") + err = enc.Encode(meta) + + if err != nil { + return err + } + if err = ioutil.WriteFile(outputPath+"/report.json", buf.Bytes(), static.CREATE_FILE_PERMISSIONS); err != nil { + return err + } + + return nil +} + +func getStats(report []ReportEntry, numberOfModules int) (s stats) { + s.total = numberOfModules + + for _, entry := range report { + switch entry.Result { + case "PASSED": + s.passed++ + case "NOTPASSED": + s.notpassed++ + case "UNSUCCESSFUL": + s.unsuccessful++ + } + snested := getStats(entry.Nested, numberOfModules) + s.passed += snested.passed + s.notpassed += snested.notpassed + s.unsuccessful += snested.unsuccessful + } + + s.notexecuted = s.total - (s.passed + s.notpassed + s.unsuccessful) + + return +} + +func saveArtifacts(report []ReportEntry, outputPath string) error { + for _, entry := range report { + commands := make(map[string]string) + hasCommand := false + outputPath += static.PATH_SEPERATOR + entry.ID + + for _, artifact := range entry.Artifacts { + if err := os.MkdirAll(outputPath, static.CREATE_DIRECTORY_PERMISSIONS); err != nil { + return err + } + + if artifact.IsFile { + filename := path.Base(filepath.ToSlash(artifact.Value)) + artifactPath := outputPath + static.PATH_SEPERATOR + filename + + source, err := os.Open(artifact.Value) + if err != nil { + return err + } + + dest, err := os.Create(artifactPath) + if err != nil { + return err + } + + _, err = io.Copy(dest, source) + if err != nil { + return err + } + + Debug(fmt.Sprintf("Artefakt gespeichert - Pfad: %v", artifactPath)) + + if err = source.Close(); err != nil { + return err + } + if err = dest.Close(); err != nil { + return err + } + + } else { + commands[artifact.Name] = artifact.Value + hasCommand = true + } + } + if hasCommand { + c, err := json.MarshalIndent(commands, "", " ") + if err != nil { + return err + } + if err = ioutil.WriteFile(outputPath+static.PATH_SEPERATOR+"commands.json", c, static.CREATE_FILE_PERMISSIONS); err != nil { + return err + } + } + + if err := saveArtifacts(entry.Nested, outputPath); err != nil { + return err + } + + outputPath = filepath.Dir(outputPath) + } + return nil +} + +func cleanArtifacts(report []ReportEntry) []ReportEntry { + for i, entry := range report { + for j, artifact := range entry.Artifacts { + if !artifact.IsFile { + report[i].Artifacts[j].Value = artifact.Name + report[i].Artifacts[j].Name = "command" + } + } + entry.Nested = cleanArtifacts(entry.Nested) + } + return report +} + +func zipFolder(folder string) error { + zipfolder, err := os.Create(folder + ".zip") + if err != nil { + return err + } + defer zipfolder.Close() + + archive := zip.NewWriter(zipfolder) + defer archive.Close() + + filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + info, err := d.Info() + if err != nil { + return err + } + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + header.Name = filepath.Join(filepath.Base(folder), strings.TrimPrefix(path, folder)) + + if d.IsDir() { + header.Name += static.PATH_SEPERATOR + } else { + header.Method = zip.Deflate + } + + writer, err := archive.CreateHeader(header) + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(writer, file) + return err + }) + return err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1170f5a --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium + +go 1.16 + +require ( + github.com/dop251/goja v0.0.0-20210427212725-462d53687b0d + github.com/pkg/errors v0.9.1 + github.com/schollz/progressbar/v3 v3.8.1 + golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 + golang.org/x/text v0.3.6 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..99805a4 --- /dev/null +++ b/go.sum @@ -0,0 +1,52 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dop251/goja v0.0.0-20210427212725-462d53687b0d h1:enuVjS1vVnToj/GuGZ7QegOAIh1jF340Sg6NXcoMohs= +github.com/dop251/goja v0.0.0-20210427212725-462d53687b0d/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/schollz/progressbar/v3 v3.8.1 h1:maiA95sku3mMHbERvCwzn/Tj6258Fm5NQf0E4L/a+5o= +github.com/schollz/progressbar/v3 v3.8.1/go.mod h1:rS3+CgxcNODZywN7C/z/7XH8gxCBLwuW5UmOUiNpOgs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 h1:C+AwYEtBp/VQwoLntUmQ/yx3MS9vmZaKNdw5eOpoQe8= +golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ec2d80c4aac0e47d563cbf22c6c48780a85067d7 GIT binary patch literal 43821 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%0D3;E@o|{C`zMjldG(yd90NhFbqdd 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

B_@ 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 literal 0 HcmV?d00001 diff --git a/main.go b/main.go new file mode 100644 index 0000000..44846fb --- /dev/null +++ b/main.go @@ -0,0 +1,245 @@ +//go:generate goversioninfo -icon=icon.ico +// Dieses Package führt das Jungbusch-Auditorium aus. Es ist unterteilt in einige Hilfs-Methoden, +// die das Ziel haben, auf den ersten Blick irrelevante Funktionalität (Error-Behandlung, Log-Ausgaben, etc.) +// aus der Main-Funktion zu entfernen um diese möglichst übersichtlich und lesbar zu halten. +// So kann man sich beispielsweise beim Debuggen leicht an den Methoden entlanghangeln und der Ablauf des +// Programms ist auf den ersten Blick ersichtlich. +package main + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/acutil" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/interpreter" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/parser" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/syntaxchecker" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-interpreter" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-parser" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/modulecontroller" + output "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/outputgenerator" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/privilege" + "github.com/pkg/errors" + "io/fs" + "os" + "path/filepath" + "runtime" + "time" +) + +func main() { + var err error + start := time.Now() + + // Temp-Ordner erstellen + initializeTemp() + + // Rechte des Prozesses bestimmen + static.HasElevatedPrivileges = privilege.HasRootPrivileges() + + // Programm-Konfiguration laden + cs, log := config_parser.LoadConfig() + + // Logger initialisieren + initializeLogger(&cs, log) + + // Betriebssystem erkennen und setzen + static.OperatingSystem = setOs(&cs) + + // Die restliche Programm-Konfiguration validieren und interpretieren + interpretConfig(&cs) + + // Module basierend auf Betriebssystem initialisieren (kompatible Module laden) + loadedModules := loadModules(&cs) + + // Einlesen der Audit-Konfigurations-Datei + Info(SeperateTitle("Audit-Parser")) + lines, err := acutil.ReadAuditConfiguration(cs.AuditConfig) + HandleError(err) + + // Audit-Konfiguration Syntax überprüfen + syntaxCheck(lines, &cs) + + // Audit-Konfiguration Parsen + auditModules, auditRequiresPrivileges, numberOfModules, err := parser.Parse(lines, loadedModules) + HandleError(err) + + // Die benötigten Privilegien mit denen des Prozesses abgleichen + checkPrivileges(auditRequiresPrivileges, &cs) + + // Die Audit-Module aus der Konfiguration interpretieren + report, err := interpreter.InterpretAudit(auditModules, numberOfModules, cs.AlwaysPrintProgress) + HandleError(err) + + // Erstellen des Reports und Speichern der Artefakte + err = output.GenerateOutput(report, cs.OutputPath, numberOfModules, cs.Zip || cs.ZipOnly, start, time.Since(start)) + HandleError(err) + + // Die Log-Datei schließen, da wir das Ergebnis nicht zippen können, solange es in dem Prozess geöffnet ist + CloseLogger() + + if cs.ZipOnly { + if err = os.RemoveAll(cs.OutputPath); err != nil { + panic(err) // Der Logger ist schon geschlossen, also können wir hier maximal panicen + } + } + + Exit() +} + +// Initialisiert einen Temp-Ordner (nur Windows) in dem über Windows-Umgebungsvariablen festgelegten Pfad. +// Hier werden möglicherweise während dem Audit-Prozess temporär zu verwendende Dateien abgelegt. +// Der Ordner wird wieder entfernt, auch wenn im Programm ein Error auftritt. +func initializeTemp() { + if runtime.GOOS == "windows" { + path, err := filepath.Abs(os.Getenv("temp")) + HandleError(err) + + // Pfad zum Ordner im Temp-Pfad resolven + path, err = util.GetAbsolutePath(path + static.PATH_SEPERATOR + "JungbuschAuditorium") + static.TempPath = path + + // Am Pfad eine Directory erstellen + err = os.Mkdir(path, static.CREATE_DIRECTORY_PERMISSIONS) + + // Wenn der Error vom Typ ErrExist ist (Ordner existiert schon), ignorieren wir ihn + if !errors.Is(err, fs.ErrExist) { + HandleError(err) + } + } +} + +// Intitialisiert den Logger. Tritt beim Initialisieren ein Fehler auf, wird die Panic-Methode des Loggers aufgerufen, +// die versucht an unterschiedlichen Pfaden einen Log zu erstellen, sodass der Nutzer über den Fehler +// informiert werden kann. +func initializeLogger(cs *ConfigStruct, log []LogMsg) { + + // Output-Pfad validieren, hier soll der Log liegen + err := config_interpreter.ValidateOutputPath(cs) + if err != nil { + LogPanic(cs, []LogMsg{ + { + Message: "Fehler beim Validieren des Output-Pfads: " + err.Error(), + Level: 1, + AlwaysPrint: false, + }, + { + Message: "Es konnte kein Log erstellt werden.", + Level: 1, + AlwaysPrint: false, + }, + }) + } + + // Output-Ordner mit den Namen aus static erstellen + err = InitializeOutput(cs) + if err != nil { + LogPanic(cs, []LogMsg{ + { + Message: "Fehler beim Erstellen des Output-Pfads: " + err.Error(), + Level: 1, + AlwaysPrint: false, + }, + { + Message: "Es konnte kein Log erstellt werden.", + Level: 1, + AlwaysPrint: false, + }, + }) + } + + // Den Logger selbst initialisieren + err = InitializeLogger(cs, log) + if err != nil { + LogPanic(cs, []LogMsg{ + { + Message: "Fehler beim Initialisieren des Loggers: " + err.Error(), + Level: 1, + AlwaysPrint: false, + }, + { + Message: "Es konnte kein Log erstellt werden.", + Level: 1, + AlwaysPrint: false, + }, + }) + } +} + +// Die Programmkonfiguration interpretieren +func interpretConfig(cs *ConfigStruct) { + // Programm-Konfiguration validieren, interpretieren + continueExecution, err := config_interpreter.InterpretConfig(cs) + HandleError(err) + + // ContinueExecution ist dann false, wenn Commandline-Parameter angegeben wurde, nach denen das + // Auditorium beendet. Bspw help, version, etc + if !continueExecution { + Exit() + } +} + +// Setzt das Betriebssystem ins Config-Struct +func setOs(cs *ConfigStruct) string { + // Betriebssystem aus Konfiguration + var err error + currOS := cs.ForceOS + if currOS == "" { + // Das Betriebssystem bestimmen, wenn keins gesetzt wurde + currOS, err = modulecontroller.GetOS() + HandleError(err) + } + return currOS +} + +// Initialisiert den Modulecontroller. +// Validiert den Syntax der Module und lädt sie. +func loadModules(cs *ConfigStruct) []ModuleSyntax { + loadedModules, err := modulecontroller.Initialize(cs.SkipModuleCompatibilityCheck) + HandleError(err) + if cs.ShowModule != "" { + InfoPrintAlways(modulecontroller.GetModuleSyntax(cs.ShowModule)) + Exit() + } + return loadedModules +} + +// Checkt den Syntax der Audit-Konfigurationsdatei +func syntaxCheck(lines []string, cs *ConfigStruct) { + // Syntax-Check + err := syntaxchecker.Syntax(lines) + HandleError(err) + + if cs.CheckSyntax { + InfoPrintAlways("Der Syntaxcheck der Audit-Konfigurationsdatei wurde fehlerfrei beendet. Das Programm beendet jetzt.") + Exit() + } +} + +// Überprüft die von der Audit-Konfiguration benötigten Privilegien und vergleicht sie mit denen des Prozesses +func checkPrivileges(auditRequiresPrivileges bool, cs *ConfigStruct) { + // Privilegien checken + if auditRequiresPrivileges { + if !static.HasElevatedPrivileges { + // Module, die Admin benötigen, wir haben aber kein Admin -> Programm beenden wenn CLI-Parameter nicht gesetzt ist + if !cs.IgnoreMissingPrivileges { + Err(Seperate()) + ErrAndExit("In der Audit-Konfiguration sind Module definiert, die Administrator, bzw. Root-Privilegien benötigen, das Jungbusch Auditorium wurde aber ohne solche Privilegien gestartet. Das Programm beendet nun.") + } else { + Warn("In der Audit-Konfiguration sind Module definiert, die Administrator, bzw. Root-Privilegien benötigen, das Jungbusch Auditorium wurde aber ohne solche Privilegien gestartet. Es wurde der Parameter angegeben, das JBA wird soweit ausgeführt wie möglich.") + } + } + } else { + if static.HasElevatedPrivileges { + // Keine Module, die Admin benötigen, wir haben aber Admin -> Warnung + Warn("In der Audit-Konfiguration sind keine Module definiert, die Administrator, bzw. Root-Privilegien benötigen, das Jungbusch-Auditorium wurde aber mit solchen gestartet.") + } + } + + // Wenn wir nur den Syntax der Konfiguration überprüfen sollen, hören wir hier auf + if cs.CheckConfiguration { + InfoPrintAlways("Die Konfigurations-Datei wurde fehlerfrei geparsed. Das Programm beendet jetzt.") + Exit() + } +} diff --git a/models/cli.go b/models/cli.go new file mode 100644 index 0000000..64d4fc1 --- /dev/null +++ b/models/cli.go @@ -0,0 +1,36 @@ +// In diesem Package werden im Jungbusch-Auditorium an unterschiedlichen Stellen verwendete Datentypen gesammelt. +// Dies hat den Vorteil, dass so Import-Loops vermieden werden. Hat das Package `Parser` beispielsweise das struct +// `AuditModule` und eine `Utility-Methode` bekommt ein solches Objekt zur Verwendung der Methode aus dem `Parser` übergeben, +// dann wurde ein Loop erschaffen, da Util den Parser wegen des structs importieren muss und der Parser wiederum Util importiert +// um die Methode verwenden zu können. Das Programm wird so nicht kompilieren. +package models + +type ConfigStruct struct { + // Programm-Parameter + AuditConfig string // Der Pfad zur Audit-Konfiguration + Config string // Der Pfad zur config.ini-Datei + OutputPath string // Der Pfad, in welcher der Output-Ordner erstellt wird + VerbosityLog int // Das Verbositylevel der Log-Datei + VerbosityConsole int // Das Verbositylevel der Konsolenausgaben + SkipModuleCompatibilityCheck bool // True, wenn alle Module unabhängig von Kompatibilität geladen werden sollen + KeepConsoleOpen bool // True, wenn beim Doppelclicken auf die Executable das Konsolenfenster nach vollständigem Durchlauf offen bleiben soll + ForceOS string // Mit diesem Parameter kann das Ergebnis des OS-Detectors überschrieben werden + IgnoreMissingPrivileges bool // True, wenn das Programm nicht abbrechen soll, wenn es nicht mit den für die Audit-Konfiguration nötigen Privilegien gestartet wurde + AlwaysPrintProgress bool // True, wenn der Fortschritt in den Modulen unabhängig vom Log-Level ausgegeben werden soll + Zip bool // True, wenn eine Zip-Datei, zusätzlich zum Output-Ordner erstellt werden soll + ZipOnly bool // True, wenn eine Zip Datei erstellt, der Output-Ordner aber entfernt werden soll + + // One-And-Done-Parameter (Programm führt keine Audits aus) + Version bool // True, wenn nur der Version-String ausgegeben werden soll + ShowModule string // Hier wird ein Modulname übergeben, dessen Informationen ausgegeben werden sollen ("all" für alle Module) + CheckConfiguration bool // True, wenn die Audit-Konfiguration geparsed, aber nicht ausgeführt werden soll + CheckSyntax bool // True, wenn die Audit-Konfiguration ausschließlich auf ihre Syntax geprüft werden soll + SaveConfiguration bool // True, wenn die aktuelle Konfiguration nach dem Parsen in die config.ini geschrieben werden soll + CreateDefaultConfig bool // True, wenn die Default-Configuration angelegt werden soll +} + +type LogMsg struct { + Message string // Die Log-Nachricht + Level int // Das Log-Level (0=None, Error, Warn, Info, 4=Debug) + AlwaysPrint bool // True, wenn die Nachricht unabhängig vom Log-Level ausgegeben werden soll +} diff --git a/models/dot_jba.go b/models/dot_jba.go new file mode 100644 index 0000000..cefffa7 --- /dev/null +++ b/models/dot_jba.go @@ -0,0 +1,57 @@ +package models + +import "strconv" + +// In diesem struct wird ein vollständiger Audit-Schritt, gemeinsam mit allen verschachtelten Modulen gespeichert. +type AuditModule struct { + Condition string // Die Bedingung, unter welcher das Modul ausgeführt wird. + ModuleName string // Der Name des Moduls, welches aufgerufen wird. Aliase werden vom Parser zum Name konvertiert. + Description string // Beschreibung des Audit-Schritts. + StepID string // Eindeutige Identifikation des Audit-Schritts. + Print string // Variablen und Werte die nach Ausführung des Moduls ausgegeben werden. + RequiresElevatedPrivileges bool // Wert kommt aus dem Modul-Initializer. Kann aus der Audit-Konfiguraion überschrieben werden. + PrivilegesOverwritten bool // True, wenn RequiresElevatedPrivileges aus der Audit-Konfiguration überschrieben wurde. + Passed string // Die Passed-Condition des Audit-Schritts. + IsGlobal bool // True, wenn der Schritt eine globale Variable enthält + Variables VariableMap // Eine Map aus allen models.Variable, auf die der Audit-Schritt Zugriff hat. + ModuleParameters ParameterMap // Eine Map aus allen Parametern, die für das Modul in der Audit-Konfig angegeben wurde. + NestedModules []AuditModule // Alle verschachtelten Audit-Schritte +} + +// Alias für [string]string Map +type ParameterMap map[string]string + +// Alias für [string]Variable Map +type VariableMap map[string]Variable + +// In diesem struct werden Variablen aus der Audit-Konfiguration gespeichert. +type Variable struct { + Name string + Value string + IsEnv bool + IsGlobal bool +} + +// In diesem struct werden Parameter gespeichert. Module bekommen Parameter aus der Audit-Konfiguration übergeben. +type Parameter struct { + ParamName string + ParamValue string +} + +// Syntax-Error-Objekte werden vom Audit-Konfigurations-Parser verwendet. +type SyntaxError struct { + ErrorMsg string // Die Error-Nachricht + LineNo int // Die mögliche Zeilen-Nummern des Errors + Line string // Der Inhalt der Zeile des Errors + Errorkeyword string // Das Schlüsselwort, an dem der Error aufgetreten ist, wenn vorhanden + Err error // Ein herkömmlicher Error +} + +// Diese Methode ist zuständig für das Parsen eines Syntax-Errors in einen String +func (e *SyntaxError) Error() string { + if e.LineNo != -1 { + return "Fehler-Nachricht: " + e.ErrorMsg + ", " + "Zeilen-Nummer: " + strconv.Itoa(e.LineNo) + ", Zeile: \"" + e.Line + "\"" + } else { + return e.ErrorMsg + } +} diff --git a/models/module.go b/models/module.go new file mode 100644 index 0000000..b87c127 --- /dev/null +++ b/models/module.go @@ -0,0 +1,37 @@ +package models + +// Mithilfe dieses structs können die Module ihre eigenen Parameter setzen. +type ModuleSyntax struct { + ModuleName string // Der Name des Moduls + ModuleAlias []string // Eine Liste aus allen Modul-Aliasen + ModuleDescription string // Die Beschreibung des Moduls + ModuleCompatibility []string // Eine Liste aus allen kompatiblen Betriebssystemen oder Wildcards + RequiresElevatedPrivileges bool // True, wenn das aktuelle Modul Administrator-/Root-Privilegien benötigt + InputParams ParameterSyntaxMap // Eine Map aus erwarteten Input-Parametern +} + +// Alias für eine Map aus string und Parametersyntax +type ParameterSyntaxMap map[string]ParameterSyntax + +// Mit diesem struct setzen die Module welche Parameter sie erwarten und in welcher Form +type ParameterSyntax struct { + ParamName string // Der Name des Parameters + ParamAlias []string // Eine Liste mit allen Parameter-Aliasen + ParamDescription string // Die Beschreibung des Parameters + IsOptional bool // True, wenn der Parameter nicht zwingend anzugeben ist +} + +// In diesem struct sammelt ein Modul seine Ergebnisse +type ModuleResult struct { + Artifacts []Artifact // Eine Liste aus allen Artefakten des Moduls + Result string // Das Ergebnis des Moduls, beeinflusst vom Grep-Parameter + ResultRaw string // Das Ergebnis des Moduls, unverändert + Err error // Ein Error, falls aufgetreten +} + +// In diesem Datentyp werden von den Modulen generierte Artefakte gespeichert +type Artifact struct { + Name string // Der Name des Artefakts + Value string // Der Wert des Artefakts + IsFile bool `json:"-"` // True, wenn das Artefakt eine Datei ist +} diff --git a/modules/0_methodhandler.go b/modules/0_methodhandler.go new file mode 100644 index 0000000..d3e57c3 --- /dev/null +++ b/modules/0_methodhandler.go @@ -0,0 +1,5 @@ +// In diesem Package sind alle Module des Jungbusch-Auditoriums enthalten. +package modules + +// Mithilfe von diesem struct kann auf die Methoden der Module per Reflection zugegriffen werden. +type MethodHandler struct{} diff --git a/modules/0_template.go b/modules/0_template.go new file mode 100644 index 0000000..558b7ac --- /dev/null +++ b/modules/0_template.go @@ -0,0 +1,33 @@ +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" +) + +func (mh *MethodHandler) TemplateInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Template", + ModuleDescription: "", + ModuleAlias: []string{}, + ModuleCompatibility: []string{}, + RequiresElevatedPrivileges: true, + InputParams: ParameterSyntaxMap{ + "param": ParameterSyntax{ + ParamName: "param", + ParamAlias: []string{}, + ParamDescription: "", + IsOptional: false, + }, + }, + } +} + +func (mh *MethodHandler) TemplateValidate(params ParameterMap) error { + + return nil +} + +func (mh *MethodHandler) Template(params ParameterMap) (r ModuleResult) { + + return +} diff --git a/modules/auditctl.go b/modules/auditctl.go new file mode 100644 index 0000000..1ce7452 --- /dev/null +++ b/modules/auditctl.go @@ -0,0 +1,60 @@ +// +build linux + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" + "regexp" +) + +func (mh *MethodHandler) AuditctlInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Auditctl", + ModuleDescription: "Mit Auditctl können die Kernel-Audit-Regeln ausgelesen werden", + ModuleAlias: []string{"auditctl"}, + ModuleCompatibility: []string{"linux"}, + RequiresElevatedPrivileges: true, + InputParams: ParameterSyntaxMap{ + "grep": ParameterSyntax{ + ParamName: "grep", + ParamAlias: []string{"name"}, + ParamDescription: "Suchbegriff, entspricht Pipen des Outputs in grep", + }, + }, + } +} + +// Auditctl liefert die zurzeit geladenen Auditregeln +func (mh *MethodHandler) Auditctl(params ParameterMap) (r ModuleResult) { + res, err := util.ExecCommand("auditctl -l") + if err != nil { + r.Err = err + return + } + + r.ResultRaw = res + + r.Result = mh.Grep(ParameterMap{ + "input": r.ResultRaw, + "grep": params["grep"], + }).Result + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "auditctl -l | grep " + params["grep"], + Value: r.Result, + }) + + return +} + +func (mh *MethodHandler) AuditctlValidate(params ParameterMap) error { + + _, err := regexp.Compile(params["grep"]) + if err != nil { + return errors.New("Modul: Auditctl - " + err.Error()) + } + + return nil +} diff --git a/modules/authselect.go b/modules/authselect.go new file mode 100644 index 0000000..9d4f451 --- /dev/null +++ b/modules/authselect.go @@ -0,0 +1,61 @@ +// +build linux + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" + "regexp" +) + +func (mh *MethodHandler) AuthselectInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Authselect", + ModuleDescription: "Mit Authselect lässt sich die Konfiguration des authselect-Profils überprüfen.", + ModuleAlias: []string{"authselect"}, + ModuleCompatibility: []string{"rhel"}, + InputParams: ParameterSyntaxMap{ + "grep": ParameterSyntax{ + ParamName: "grep", + IsOptional: true, + ParamDescription: "Optionaler Suchbegriff, entspricht dem Pipen des Outputs in grep", + }, + }, + } +} + +// ExecuteCommand führt den übergebenen Befehl aus und speichert das Ergebnis in einem String. +func (mh *MethodHandler) Authselect(params ParameterMap) (r ModuleResult) { + res, err := util.ExecCommand("authselect current") + if err != nil { + r.Err = err + return + } + + r.ResultRaw = res + r.Result = r.ResultRaw + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "authselect current", + Value: r.ResultRaw, + }) + + if params["grep"] != "" { + r.Result = mh.Grep(ParameterMap{ + "input": r.ResultRaw, + "grep": params["grep"], + }).Result + } + + return +} + +func (mh *MethodHandler) AuthselectValidate(params ParameterMap) error { + _, err := regexp.Compile(params["grep"]) + if err != nil { + return errors.New("Modul: Authselect - " + err.Error()) + // besser: return errors.Wrap(err, "Modul: Authselect") + } + + return nil +} diff --git a/modules/awk_script.go b/modules/awk_script.go new file mode 100644 index 0000000..3a94726 --- /dev/null +++ b/modules/awk_script.go @@ -0,0 +1,56 @@ +// +build linux + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" +) + +func (mh *MethodHandler) AwkScriptInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "AwkScript", + ModuleDescription: "Awk ist eine Skriptsprache zum Editieren und Analysieren von Texten. AwkScript führt ein Skript auf die Input-Datei aus", + ModuleAlias: []string{"awk", "awkScript", "awk_script"}, + ModuleCompatibility: []string{"linux"}, + InputParams: ParameterSyntaxMap{ + "input": ParameterSyntax{ + ParamName: "input", + ParamDescription: "String, auf den das Skript angewendet wird", + }, + "awkscript": ParameterSyntax{ + ParamName: "awkscript", + ParamAlias: []string{"script"}, + ParamDescription: "Awk-Script, welches ausgeführt werden soll", + }, + "separator": ParameterSyntax{ + ParamName: "separator", + IsOptional: true, + ParamDescription: "Legt den Field-Separator fest", + }, + }, + } +} + +// Awk ist eine Skriptsprache zum Editieren und Analysieren von Texten. AwkScript führt ein Awk-Script, auf die Input-Datei aus +func (mh *MethodHandler) AwkScript(params ParameterMap) (r ModuleResult) { + var separator string + if params["separator"] != "" { + separator = "-F" + params["separator"] + } + res, err := util.ExecCommand("awk " + separator + " " + params["awkscript"]) + if err != nil { + r.Err = err + return + } + + r.ResultRaw = res + r.Artifacts = append(r.Artifacts, Artifact{Name: "awk " + params["awkscript"] + params["input"], Value: r.ResultRaw}) + r.Result = r.ResultRaw + return +} + +func (mh *MethodHandler) AwkScriptValidate(params ParameterMap) error { + // ? + return nil +} diff --git a/modules/bash_script.go b/modules/bash_script.go new file mode 100644 index 0000000..a8c7f92 --- /dev/null +++ b/modules/bash_script.go @@ -0,0 +1,46 @@ +// +build linux + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "os/exec" +) + +func (mh *MethodHandler) BashScriptInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "BashScript", + ModuleDescription: "BashScript führt das verlinkte Bash-Script aus. Als Ergebnis wird der Output des Skripts erhalten.", + ModuleAlias: []string{"bash_script", "bashScript"}, + ModuleCompatibility: []string{"linux"}, + InputParams: ParameterSyntaxMap{ + "script": ParameterSyntax{ + ParamName: "script", + ParamAlias: []string{}, + ParamDescription: "Das auszuführende Bash-Script", + }, + }, + } +} + +// ExecuteCommand führt den übergebenen Befehl aus und speichert das Ergebnis in einem String. +func (mh *MethodHandler) BashScript(params ParameterMap) (r ModuleResult) { + script, err := util.GetAbsolutePath(params["script"]) + if err != nil { + r.Err = err + return + } + out, err := exec.Command("bash", script).CombinedOutput() + if err != nil { + r.Err = err + return + } + + r.ResultRaw = string(out) + r.Result = r.ResultRaw + + r.Artifacts = append(r.Artifacts, Artifact{Name: params["script"], Value: r.ResultRaw}) + + return +} diff --git a/modules/check_partition.go b/modules/check_partition.go new file mode 100644 index 0000000..56c3c8f --- /dev/null +++ b/modules/check_partition.go @@ -0,0 +1,78 @@ +// +build linux + +package modules + +import ( + "errors" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "regexp" +) + +func (mh *MethodHandler) CheckPartitionInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "CheckPartition", + ModuleDescription: "CheckPartition gibt alle eingehängten Datenträger aus. Ist der grep-Parameter gesetzt, werden auch Zeilen zurückgegeben, in denen das Pattern gefunden wurde.", + ModuleAlias: []string{"mount"}, + ModuleCompatibility: []string{"linux"}, + InputParams: ParameterSyntaxMap{ + "grep": ParameterSyntax{ + ParamName: "grep", + IsOptional: true, + ParamDescription: "Optionaler Suchbegriff, entspricht dem Pipen des Outputs in grep", + }, + "vgrep": ParameterSyntax{ + ParamName: "vgrep", + IsOptional: true, + ParamDescription: "Optionaler Suchbegriff, entspricht dem Pipen des Outputs in grep -v", + }, + }, + } +} + +// CheckPartition gibt alle eingehängten Datenträger aus. +// Ist der grep-Parameter gesetzt, werden auch Zeilen zurückgegeben, in denen +// das Pattern gefunden wurde. +func (mh *MethodHandler) CheckPartition(params ParameterMap) (r ModuleResult) { + res, err := util.ExecCommand("mount") + if err != nil { + r.Err = err + return + } + + r.ResultRaw = res + r.Result = r.ResultRaw + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "mount", + Value: r.ResultRaw, + }) + if params["grep"] != "" { + r.Result = mh.Grep(ParameterMap{ + "input": r.ResultRaw, + "grep": params["grep"], + }).Result + } + + if params["vgrep"] != "" { + r.Result = mh.Grep(ParameterMap{ + "input": r.Result, + "grep": "-v " + params["grep"], + }).Result + } + + return +} + +func (mh *MethodHandler) CheckPartitionValidate(params ParameterMap) error { + _, err := regexp.Compile(params["grep"]) + if err != nil { + return errors.New("Modul: CheckPartition - " + err.Error()) + } + + _, err = regexp.Compile(params["vgrep"]) + if err != nil { + return errors.New("Modul: CheckPartition - " + err.Error()) + } + + return nil +} diff --git a/modules/execute_command.go b/modules/execute_command.go new file mode 100644 index 0000000..c0dd92c --- /dev/null +++ b/modules/execute_command.go @@ -0,0 +1,67 @@ +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "strings" +) + +func (mh *MethodHandler) ExecuteCommandInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "ExecuteCommand", + ModuleDescription: "ExecuteCommand führt den übergebenen Befehl aus und überprüft optional, ob der angegebene Suchbegriff im Ergebnis vorhanden ist.", + ModuleAlias: []string{"execute_command", "executeCommand"}, + ModuleCompatibility: []string{"all"}, + InputParams: ParameterSyntaxMap{ + "command": ParameterSyntax{ + ParamName: "command", + ParamAlias: []string{"cmd"}, + ParamDescription: "Der auszuführende Befehl", + }, + "grep": ParameterSyntax{ + ParamName: "grep", + IsOptional: true, + ParamDescription: "Optionaler Suchbegriff, entspricht dem Pipen des Outputs in grep", + }, + }, + } +} + +// ExecuteCommand führt den übergebenen Befehl aus und speichert das Ergebnis in einem String. +func (mh *MethodHandler) ExecuteCommand(params ParameterMap) (r ModuleResult) { + out, err := util.ExecCommand(params["command"]) + if err != nil { + r.Err = err + return + } + + out = strings.ReplaceAll(out, "\r", "") + r.ResultRaw = out + r.Result = r.ResultRaw + + if params["grep"] != "" { + r.Result = mh.Grep(ParameterMap{ + "input": r.ResultRaw, + "grep": params["grep"], + }).Result + } + + r.Artifacts = append(r.Artifacts, Artifact{Name: params["command"], Value: r.ResultRaw}) + + return +} + +func (mh *MethodHandler) ExecuteCommandValidate(params ParameterMap) error { + + // if params["command"] == "" { + // return errors.New("der Command-Parameter darf nicht leer sein") + // } + // + // splitCommand := strings.Fields(params["command"]) + // + // _, err := exec.Command(splitCommand[0], splitCommand[1:]...).Output() + // if err != nil { + // return errors.New("Modul: ExecuteCommand - " + err.Error()) + // } + return nil +} diff --git a/modules/file_content.go b/modules/file_content.go new file mode 100644 index 0000000..b69b0d2 --- /dev/null +++ b/modules/file_content.go @@ -0,0 +1,98 @@ +package modules + +import ( + "bytes" + "errors" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "io/fs" + "os" + "path/filepath" + "regexp" +) + +func (mh *MethodHandler) FileContentInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "FileContent", + ModuleDescription: "FileContent gibt den Inhalt der angegebenen Datei. Ist der grep-parameter gesetzt, werden nur die Zeilen zurückgegeben, in denen das Pattern gefunden wurde.", + ModuleAlias: []string{"file_content", "fileContent"}, + ModuleCompatibility: []string{"all"}, + InputParams: ParameterSyntaxMap{ + "file": ParameterSyntax{ + ParamName: "file", + ParamAlias: []string{"datei"}, + ParamDescription: "Pfad zur Datei", + }, + "grep": ParameterSyntax{ + ParamName: "grep", + IsOptional: true, + ParamDescription: "Optionaler Suchbegriff, entspricht Pipen des Outputs in grep", + }, + }, + } +} + +// FileContent gibt den Inhalt der angegebenen Datei als String zurück. +// Ist der grep-Parameter gesetzt, werden auch Zeilen zurückgegeben, in denen +// das Pattern gefunden wurde. +func (mh *MethodHandler) FileContent(params ParameterMap) (r ModuleResult) { + // Finden von allen passenden Dateien, falls Wildcards angegeben wurden + glob, err := filepath.Glob(params["file"]) + if err != nil { + r.Err = err + return + } + + if glob == nil { + r.Err = errors.New("Datei wurde nicht gefunden: " + params["file"]) + return + } + + // Iteriert über jede gefundene Datei + for _, file := range glob { + file, err = util.GetAbsolutePath(file) + if err != nil { + r.Err = err + return + } + + r.Artifacts = append(r.Artifacts, Artifact{Name: "file", Value: file, IsFile: true}) + + content, err := os.ReadFile(file) + if err != nil { + if errors.Is(err, fs.ErrPermission) { + r.Err = errors.New("Zur Ausführung des Moduls werden Administrator, bzw. Root-Privilegien benötigt.") + } else { + r.Err = err + } + return + } + + // Entfernen des CarriageReturn-Characters aus CRLF-formatierten Dateien + content = bytes.ReplaceAll(content, []byte("\r"), []byte("")) + r.ResultRaw += "\n" + string(content) + + // Falls der Grep-Parameter angegeben wurde, wird hier grep ausgeführt + if params["grep"] != "" { + r.Result += mh.Grep(ParameterMap{ + "input": r.ResultRaw, + "grep": params["grep"], + }).Result + } + } + + return +} + +func (mh *MethodHandler) FileContentValidate(params ParameterMap) error { + if _, err := util.GetAbsolutePath(params["file"]); err != nil { + return errors.New("Modul: FileContent - " + err.Error()) + } + + _, err := regexp.Compile(params["grep"]) + if err != nil { + return errors.New("Modul: FileContent - " + err.Error()) + } + + return nil +} diff --git a/modules/grep.go b/modules/grep.go new file mode 100644 index 0000000..04d7b96 --- /dev/null +++ b/modules/grep.go @@ -0,0 +1,69 @@ +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/pkg/errors" + "regexp" + "strings" +) + +func (mh *MethodHandler) GrepInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Grep", + ModuleDescription: "Grep dient als Suchfunktion und kann in verschiedenen Modulen aufgerufen werden.", + ModuleCompatibility: []string{"all"}, + InputParams: ParameterSyntaxMap{ + "input": ParameterSyntax{ + ParamName: "input", + ParamDescription: "Übergebener String, in dem gesucht werden soll", + }, + "grep": ParameterSyntax{ + ParamName: "grep", + ParamDescription: "Suchbegriff/Regex-Ausdruck", + }, + }, + } +} + +// Grep liefert die Zeile, wo der Suchbegriff im Input übereinstimmt als String zurück. +func (mh *MethodHandler) Grep(params ParameterMap) (r ModuleResult) { + invert := false + split := strings.SplitN(params["grep"], " ", 2) + grep := "" + + if len(split) == 1 { + grep = split[0] + } else { + if split[0][0] == '-' { + if strings.Contains(split[0], "i") { + grep = "(?i)" + split[1] + } + if strings.Contains(split[0], "v") { + invert = true + } + } else { + grep = params["grep"] + } + } + + re, err := regexp.Compile(grep) + if err != nil { + r.Err = err + return + } + + for _, line := range strings.SplitAfter(params["input"], "\n") { + if !invert && re.MatchString(line) || invert && !re.MatchString(line) { + r.Result += line + } + } + return +} + +func (mh *MethodHandler) GrepValidate(params ParameterMap) error { + _, err := regexp.Compile(params["grep"]) + if err != nil { + return errors.New("Modul: Auditctl - " + err.Error()) + } + return nil +} diff --git a/modules/is_file.go b/modules/is_file.go new file mode 100644 index 0000000..1cd5e04 --- /dev/null +++ b/modules/is_file.go @@ -0,0 +1,42 @@ +package modules + +import ( + "fmt" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" +) + +func (mh *MethodHandler) IsFileInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "IsFile", + ModuleDescription: "IsFile prüft ob eine Datei existiert.", + ModuleCompatibility: []string{"all"}, + InputParams: ParameterSyntaxMap{ + "path": ParameterSyntax{ + ParamName: "path", + ParamDescription: "Pfad zur Datei", + }, + }, + } +} + +func (mh *MethodHandler) IsFile(params ParameterMap) (r ModuleResult) { + path := params["path"] + isFile := util.IsFile(path) + if isFile { + r.ResultRaw = fmt.Sprintf("Dateipfad: %v, Ist eine Datei: true", path) + r.Result = "true" + } else { + r.ResultRaw = fmt.Sprintf("Dateipfad: %v, Ist eine Datei: false", path) + r.Result = "false" + } + return +} + +func (mh *MethodHandler) IsFileValidate(params ParameterMap) error { + if _, err := util.GetAbsolutePath(params["path"]); err != nil { + return errors.New("Der angegebene Pfad ist ungültig.") + } + return nil +} diff --git a/modules/is_installed.go b/modules/is_installed.go new file mode 100644 index 0000000..37a8b06 --- /dev/null +++ b/modules/is_installed.go @@ -0,0 +1,42 @@ +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "os/exec" +) + +func (mh *MethodHandler) IsInstalledInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "IsInstalled", + ModuleDescription: "IsInstalled überprüft, ob das angegebene Package installiert ist.", + ModuleAlias: []string{"isInstalled", "is_installed", "installed"}, + ModuleCompatibility: []string{"linux", "darwin"}, + InputParams: ParameterSyntaxMap{ + "package": ParameterSyntax{ + ParamName: "package", + ParamAlias: []string{"name", "packagename", "pkg"}, + ParamDescription: "Name des Packages", + }, + }, + } +} + +// IsInstalled liefert Informationen darüber, ob das angegeben Package installiert ist. +func (mh *MethodHandler) IsInstalled(params ParameterMap) (r ModuleResult) { + res, err := exec.LookPath(params["package"]) + if err != nil { + r.ResultRaw = res + r.Result = "false" + } else { + // Programm ist installiert + r.ResultRaw = params["package"] + " ist installiert." + r.Result = "true" + } + + return +} + +func (mh *MethodHandler) IsInstalledValidate(params ParameterMap) error { + + return nil +} diff --git a/modules/is_not_installed.go b/modules/is_not_installed.go new file mode 100644 index 0000000..5aa53ce --- /dev/null +++ b/modules/is_not_installed.go @@ -0,0 +1,41 @@ +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "os/exec" +) + +func (mh *MethodHandler) IsNotInstalledInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "IsNotInstalled", + ModuleDescription: "IsNotInstalled überprüft, ob das angegebene Package nicht installiert ist.", + ModuleAlias: []string{"isNotInstalled", "is_not_installed", "notInstalled", "not_installed"}, + ModuleCompatibility: []string{"linux", "darwin"}, + InputParams: ParameterSyntaxMap{ + "package": ParameterSyntax{ + ParamName: "package", + ParamAlias: []string{"name", "packagename", "pkg"}, + ParamDescription: "Name des Packages", + }, + }, + } +} + +// IsInstalled liefert Informationen darüber, ob das angegebene Package nicht installiert ist. +func (mh *MethodHandler) IsNotInstalled(params ParameterMap) (r ModuleResult) { + res, err := exec.LookPath(params["package"]) + if err != nil { + r.ResultRaw = params["package"] + " scheint nicht installiert zu sein." + r.Result = "true" + } else { + // Programm ist installiert + r.ResultRaw = res + r.Result = "false" + } + return +} + +func (mh *MethodHandler) IsNotInstalledValidate(params ParameterMap) error { + + return nil +} diff --git a/modules/modprobe.go b/modules/modprobe.go new file mode 100644 index 0000000..b39a818 --- /dev/null +++ b/modules/modprobe.go @@ -0,0 +1,71 @@ +// +build linux + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" +) + +func (mh *MethodHandler) ModprobeInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Modprobe", + ModuleDescription: "Modprobe simuliert das Laden des angegebenen Moduls zur Laufzeit des Systems und speichert das Ergebnis ausführlich ab. Daraufhin wird überprüft, ob das angegebene Modul aktuell geladen ist.", + ModuleCompatibility: []string{"linux"}, + RequiresElevatedPrivileges: true, + InputParams: ParameterSyntaxMap{ + "name": ParameterSyntax{ + ParamName: "name", + ParamAlias: []string{}, + ParamDescription: "Name des Moduls", + }, + }, + } +} + +// Modprobe simuliert das Laden des angegebenen Moduls zur Laufzeit des Systems und speichert das Ergebnis ausführlich ab. +// Daraufhin wird überprüft, ob das angegebene Modul aktuell geladen ist. +func (mh *MethodHandler) Modprobe(params ParameterMap) (r ModuleResult) { + res_modprobe, err := util.ExecCommand("modprobe -n -v " + params["name"]) + if err != nil { + r.Err = err + return + } + + r.ResultRaw = "modprobe: " + res_modprobe + r.Artifacts = append(r.Artifacts, Artifact{Name: "modprobe -n -v " + params["name"], Value: res_modprobe}) + + if res_modprobe != "install /bin/true\n" { + Debug("Ergebnis von 'modprobe' != 'install /bin/true'") + r.Result = "false" + return + } + + res_lsmod, err := util.ExecCommand("lsmod " + params["name"]) + if err != nil { + r.Err = err + return + } + r.ResultRaw += "\nlsmod: " + res_lsmod + r.Artifacts = append(r.Artifacts, Artifact{Name: "lsmod " + params["name"], Value: res_lsmod}) + + r.Result = mh.Grep(ParameterMap{ + "input": res_lsmod, + "grep": params["name"], + }).Result + + if r.Result != "" { + Debug("Ergebnis von 'lsmod | grep " + params["name"] + "' != ") + r.Result = "false" + return + } + + r.Result = "true" + return +} + +func (mh *MethodHandler) ModprobeValidate(params ParameterMap) error { + + return nil +} diff --git a/modules/nft_list_ruleset.go b/modules/nft_list_ruleset.go new file mode 100644 index 0000000..0577861 --- /dev/null +++ b/modules/nft_list_ruleset.go @@ -0,0 +1,74 @@ +// +build linux + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" + "regexp" +) + +func (mh *MethodHandler) NftListRulesetInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "NftListRuleset", + ModuleDescription: "Mit NftListRuleset lässt sich das Ruleset von nftables analysieren", + ModuleAlias: []string{"nftListRuleset", "nft_list_ruleset"}, + ModuleCompatibility: []string{"linux"}, + RequiresElevatedPrivileges: true, + InputParams: ParameterSyntaxMap{ + "awk": ParameterSyntax{ + ParamName: "awk", + ParamDescription: "Optionales Skript", + IsOptional: true, + }, + "grep": ParameterSyntax{ + ParamName: "grep", + ParamDescription: "Suchbegriff, entspricht dem Pipen des Outputs in grep", + }, + }, + } +} + +// NftListRuleset listet das Ruleset von nftables auf +func (mh *MethodHandler) NftListRuleset(params ParameterMap) (r ModuleResult) { + res, err := util.ExecCommand("nft list ruleset") + if err != nil { + r.Err = err + return + } + + r.ResultRaw = res + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "nft list ruleset", + Value: r.ResultRaw, + }) + + if params["awk"] != "" { + r.Result = mh.AwkScript(ParameterMap{ + "input": r.ResultRaw, + "awkscript": params["awk"], + }).Result + + r.Result = mh.Grep(ParameterMap{ + "input": r.Result, + "grep": params["grep"], + }).Result + } else { + r.Result = mh.Grep(ParameterMap{ + "input": r.ResultRaw, + "grep": params["grep"], + }).Result + } + + return +} + +func (mh *MethodHandler) NftListRulesetValidate(params ParameterMap) error { + _, err := regexp.Compile(params["grep"]) + if err != nil { + return errors.New("Modul: NftListRuleset - " + err.Error()) + } + + return nil +} diff --git a/modules/permissions.go b/modules/permissions.go new file mode 100644 index 0000000..d0f72da --- /dev/null +++ b/modules/permissions.go @@ -0,0 +1,38 @@ +package modules + +import ( + "fmt" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "os" +) + +func (mh *MethodHandler) PermissionsInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Permissions", + ModuleDescription: "Permissions gibt die Berechtigungen der/des angegebenen Datei/Ordners zurück.", + ModuleAlias: []string{"perms", "permissions"}, + ModuleCompatibility: []string{"all"}, + InputParams: ParameterSyntaxMap{ + "path": ParameterSyntax{ + ParamName: "path", + ParamAlias: []string{"pfad"}, + ParamDescription: "Pfad der/des zu überprüfenden Datei/Ordners", + }, + }, + } +} + +// Permissions gibt Berechtigungen der/des übergebenden Datei/Ordners in numerischer Form als String zurück. +func (mh *MethodHandler) Permissions(params ParameterMap) (r ModuleResult) { + info, err := os.Stat(params["path"]) + if err != nil { + r.Err = err + return + } + r.Result = fmt.Sprintf("%#o", info.Mode().Perm()) + return +} + +func (mh *MethodHandler) PermissionsValidate(params ParameterMap) error { + return nil +} diff --git a/modules/script.go b/modules/script.go new file mode 100644 index 0000000..8a5e05a --- /dev/null +++ b/modules/script.go @@ -0,0 +1,174 @@ +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "github.com/dop251/goja" + "github.com/pkg/errors" + "reflect" + "strings" +) + +func (mh *MethodHandler) ScriptInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Script", + ModuleDescription: "Mit Script lässt sich JavaScript-Code ausführen. Es lässt sich auf alle anderen Module zugreifen.", + ModuleAlias: []string{"javascript", "js"}, + ModuleCompatibility: []string{"all"}, + InputParams: ParameterSyntaxMap{ + "script": ParameterSyntax{ + ParamName: "script", + ParamAlias: []string{"js"}, + ParamDescription: "Auszuführendes Script", + }, + }, + } +} + +// Script führt JavaScript-Code aus um zum Beispiel auf andere Module zugreifen zu können +func (mh *MethodHandler) Script(params ParameterMap, variables *VariableMap) (r ModuleResult) { + + vm := goja.New() + vm.SetFieldNameMapper( + goja.UncapFieldNameMapper(), + ) + if err := vm.Set("params", vm.ToValue(ParameterMap{})); err != nil { + r.Err = err + return + } + + if err := vm.Set("newResult", newResult); err != nil { + r.Err = err + return + } + + if err := initModules(vm, mh); err != nil { + r.Err = err + return + } + + if err := initLogging(vm); err != nil { + r.Err = err + return + } + + if err := initVariables(vm, *variables); err != nil { + r.Err = err + return + } + + res, err := vm.RunString(params["script"]) + if err != nil { + if ierr, ok := err.(*goja.InterruptedError); ok { + r.Err = ierr.Value().(ModuleResult).Err + return + } else { + r.Err = err + return + } + } + + r, ok := res.Export().(ModuleResult) + if !ok { + // Das Skript liefert kein Ergebnis vom Typ ModuleResult + t := "" + switch res.ExportType().(type) { + case nil: + t = "nil" + default: + t = res.ExportType().String() + } + r.Err = errors.New("Das letzte Statement im Script muss vom Typ ModuleResult sein, ist " + t) + return + } + + variables = exportVariables(vm, *variables) + + return +} + +// Initialisiert den MethodHandler in JS mit allen Modulen +func initModules(vm *goja.Runtime, mh *MethodHandler) (err error) { + artifacts := make([]Artifact, 0) + + // Iteriert über alle Module und definiert sie in der JS-VM + t := reflect.TypeOf(mh) + for i := 0; i < t.NumMethod(); i++ { + m := t.Method(i) + if !strings.HasSuffix(m.Name, "Init") && !strings.HasSuffix(m.Name, "Validate") { + err = vm.Set(m.Name, func(params ParameterMap) ModuleResult { + in := []reflect.Value{reflect.ValueOf(mh), reflect.ValueOf(params)} + res := m.Func.Call(in)[0].Interface().(ModuleResult) + if res.Err != nil { + vm.Interrupt(res) + } + // Speichert Artefakte für alle ausgeführten Module + + artifacts = append(artifacts, res.Artifacts...) + res.Artifacts = artifacts + + return res + }) + if err != nil { + return err + } + } + } + return +} + +func (mh *MethodHandler) ScriptValidate(params ParameterMap) error { + _, err := goja.Compile("", params["script"], false) + if err != nil { + return errors.New(params["script"] + "\n" + err.Error()) + } else { + return nil + } +} + +func initLogging(vm *goja.Runtime) (err error) { + if err = vm.Set("info", logger.Info); err != nil { + return err + } + if err = vm.Set("warn", logger.Warn); err != nil { + return err + } + if err = vm.Set("err", logger.Err); err != nil { + return err + } + if err = vm.Set("debug", logger.Debug); err != nil { + return err + } + return +} + +func initVariables(vm *goja.Runtime, variables VariableMap) (err error) { + for _, v := range variables { + if !v.IsEnv { + if err = vm.Set(strings.Trim(v.Name, "%"), v.Value); err != nil { + return + } + } + } + return +} + +func exportVariables(vm *goja.Runtime, variables VariableMap) *VariableMap { + for _, v := range variables { + if !v.IsEnv { + variables[v.Name] = Variable{ + Name: v.Name, + Value: vm.Get(strings.Trim(v.Name, "%")).Export().(string), + } + } + } + return &variables +} + +func newResult(result, resultRaw string, err error) ModuleResult { + return ModuleResult{ + Result: result, + ResultRaw: resultRaw, + Err: err, + } +} diff --git a/modules/sshd.go b/modules/sshd.go new file mode 100644 index 0000000..aa6500b --- /dev/null +++ b/modules/sshd.go @@ -0,0 +1,59 @@ +// +build linux + +package modules + +import ( + "errors" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "regexp" +) + +func (mh *MethodHandler) SshdInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Sshd", + ModuleDescription: "Sshd liest die Informationen aus der sshd Config-Datei aus ", + ModuleAlias: []string{"sshd"}, + ModuleCompatibility: []string{"linux"}, + InputParams: ParameterSyntaxMap{ + "grep": ParameterSyntax{ + ParamName: "grep", + IsOptional: true, + ParamDescription: "Optionaler Suchbegriff, entspricht dem Pipen des Outputs in grep", + }, + }, + } +} + +// Sshd liest die Informationen aus der sshd Config-Datei aus +func (mh *MethodHandler) Sshd(params ParameterMap) (r ModuleResult) { + res, err := util.ExecCommand("sshd -T") + if err != nil { + r.Err = err + return + } + + r.ResultRaw = res + r.Result = r.ResultRaw + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "sshd -T", + Value: r.ResultRaw, + }) + if params["grep"] != "" { + r.Result = mh.Grep(ParameterMap{ + "input": r.ResultRaw, + "grep": params["grep"], + }).Result + } + + return +} + +func (mh *MethodHandler) SshdValidate(params ParameterMap) error { + _, err := regexp.Compile(params["grep"]) + if err != nil { + return errors.New("Modul: Sshd - " + err.Error()) + } + + return nil +} diff --git a/modules/stat.go b/modules/stat.go new file mode 100644 index 0000000..af4721e --- /dev/null +++ b/modules/stat.go @@ -0,0 +1,54 @@ +// +build linux + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" +) + +func (mh *MethodHandler) StatInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Stat", + ModuleDescription: "Mit dem Befehl stat lassen sich Zugriffs- und Änderungs-Zeitstempel von Dateien und Ordnern anzeigen. Weiterhin werden Informationen zu Rechten, zu Besitzer und Gruppe und zum Dateityp ausgegeben.", + ModuleAlias: []string{"stat"}, + ModuleCompatibility: []string{"linux"}, + RequiresElevatedPrivileges: true, + InputParams: ParameterSyntaxMap{ + "file": ParameterSyntax{ + ParamName: "file", + ParamAlias: []string{"datei"}, + ParamDescription: "Pfad zur Datei", + }, + }, + } +} + +// Mit dem Befehl Stat lassen sich Zugriffs- und Änderungs-Zeitstempel von Dateien und Ordnern anzeigen. +// Weiterhin werden Informationen zu Rechten, zu Besitzer und Gruppe und zum Dateityp ausgegeben. +func (mh *MethodHandler) Stat(params ParameterMap) (r ModuleResult) { + res, err := util.ExecCommand("stat " + params["file"]) + if err != nil { + r.Err = err + return + } + + r.ResultRaw = res + r.Result = mh.Grep(ParameterMap{ + "input": r.ResultRaw, + "grep": "Uid:", + }).Result + + r.Artifacts = append(r.Artifacts, Artifact{Name: "stat " + params["file"], Value: r.Result}) + + return +} + +func (mh *MethodHandler) StatValidate(params ParameterMap) error { + if !util.IsFile(params["file"]) { + return errors.New("Datei ist nicht vorhanden") + } else { + return nil + } +} diff --git a/modules/sysctl.go b/modules/sysctl.go new file mode 100644 index 0000000..abc1f10 --- /dev/null +++ b/modules/sysctl.go @@ -0,0 +1,46 @@ +// +build linux + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" +) + +func (mh *MethodHandler) SysctlInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Sysctl", + ModuleDescription: "Sysctl wird dazu verwendet, Kernelparameter zur Laufzeit zu ändern. Die verfügbaren Parameter sind unter /proc/sys/ aufgelistet. Für die sysctl-Unterstützung in Linux ist Procfs notwendig. Sie können sysctl sowohl zum Lesen als auch zum Schreiben von Sysctl-Daten verwenden.", + ModuleAlias: []string{"sysctl"}, + ModuleCompatibility: []string{"linux"}, + InputParams: ParameterSyntaxMap{ + "kernelparameter": ParameterSyntax{ + ParamName: "kernelparameter", + ParamAlias: []string{"kernelparam"}, + ParamDescription: "Bezeichnet den Namen des Schlüssels, aus dem gelesen werden soll.", + }, + }, + } +} + +//sysctl wird dazu verwendet, Kernelparameter zur Laufzeit zu ändern. Die verfügbaren +//Parameter sind unter /proc/sys/ aufgelistet. Für die sysctl-Unterstützung in Linux ist +//Procfs notwendig. Sie können sysctl sowohl zum Lesen als auch zum Schreiben von +//Sysctl-Daten verwenden. +func (mh *MethodHandler) Sysctl(params ParameterMap) (r ModuleResult) { + res_param1, err := util.ExecCommand("sysctl " + params["kernelparameter"]) + if err != nil { + r.Err = err + return + } + r.ResultRaw = res_param1 + r.Artifacts = append(r.Artifacts, Artifact{Name: "sysctl " + params["kernelparameter"], Value: r.ResultRaw}) + + r.Result = r.ResultRaw + return +} + +func (mh *MethodHandler) SysctlValidate(params ParameterMap) error { + + return nil +} diff --git a/modules/systemctl.go b/modules/systemctl.go new file mode 100644 index 0000000..e8ec96e --- /dev/null +++ b/modules/systemctl.go @@ -0,0 +1,43 @@ +// +build linux + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" +) + +func (mh *MethodHandler) SystemctlInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "Systemctl", + ModuleDescription: "Systemctl überprüft, ob die angegebene Unitdatei aktiviert ist.", + ModuleAlias: []string{"systemctl"}, + ModuleCompatibility: []string{"linux"}, + InputParams: ParameterSyntaxMap{ + "unitdatei": ParameterSyntax{ + ParamName: "unitdatei", + ParamAlias: []string{"unitname"}, + ParamDescription: "Name der Unitdatei", + }, + }, + } +} + +// Systemctl überprüft, ob die angegebene Unitdatei aktiviert ist. +func (mh *MethodHandler) Systemctl(params ParameterMap) (r ModuleResult) { + res, err := util.ExecCommand("systemctl is-enabled " + params["unitdatei"]) + if err != nil { + r.Err = err + return + } + + r.ResultRaw = res + r.Artifacts = append(r.Artifacts, Artifact{Name: "systemctl is-enabled " + params["unitdatei"], Value: r.ResultRaw}) + r.Result = r.ResultRaw + return +} + +func (mh *MethodHandler) SystemctlValidate(params ParameterMap) error { + + return nil +} diff --git a/modules/win_audit_policy_query.go b/modules/win_audit_policy_query.go new file mode 100644 index 0000000..7f86e82 --- /dev/null +++ b/modules/win_audit_policy_query.go @@ -0,0 +1,68 @@ +// +build windows + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" + s "strings" +) + +func (mh *MethodHandler) AuditPolicyQueryInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "AuditPolicyQuery", + ModuleAlias: []string{"auditpolicyquery", "auditpol"}, + ModuleDescription: "AuditPolicyQuery ermöglicht den Bereich 'Advanced Audit Policy Configuration' der Security Settings auszulesen.", + ModuleCompatibility: []string{"windows"}, + RequiresElevatedPrivileges: true, + InputParams: ParameterSyntaxMap{ + "guid": ParameterSyntax{ + ParamName: "guid", + ParamDescription: "GUID der jeweiligen Value. Bitte im Benutzerhandbuch oder Internet nachschlagen.", + }, + }, + } +} + +func (mh *MethodHandler) AuditPolicyQuery(params ParameterMap) (r ModuleResult) { + guid := params["guid"] + guid = s.ReplaceAll(guid, " ", "") + + // Fehlende Klammern um GUID setzen + if !s.HasPrefix(guid, "{") && !s.HasSuffix(guid, "}") { + guid = "{" + guid + "}" + } + + if s.Count(guid, "{") != 1 && s.Count(guid, "}") != 1 { + r.Err = errors.New("Die Klammern der GUID sind ungültig.") + return + } + + // Status der Erweiterten Überwachungsrichtlinienkonfiguration abfragen + status, err := util.ExecCommand("cmd.exe /c \"auditpol /get /subcategory:" + params["guid"] + "\"") + + if err != nil { + r.Err = err + return + } + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "auditpol /get /subcategory:" + params["guid"], + Value: status, + }) + + r.ResultRaw = status + + // Prüfen ob ein Fehler aufgetreten ist + if s.Contains(status, "/?") || s.Contains(status, "0x00000057") { + r.Err = errors.New("Die GUID ist ungültig.") + return + } + + // !!ACHTUNG!! + // r.Result wird in der jeweiligen Sprache des OS zurückgegeben. + // Bsp. Mögliche deutsche Wörter: Erfolg, und, Fehler, Keine Überwachung + r.Result = s.TrimSpace(s.ReplaceAll(status[s.LastIndex(status, " ")+2:], "\r", "")) + return +} diff --git a/modules/win_dump_security_settings.go b/modules/win_dump_security_settings.go new file mode 100644 index 0000000..139a954 --- /dev/null +++ b/modules/win_dump_security_settings.go @@ -0,0 +1,68 @@ +// +build windows + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" +) + +func (mh *MethodHandler) DumpSecuritySettingsInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "DumpSecuritySettings", + ModuleDescription: "DumpSecuritySettings ermöglicht das Dumpen der aktuellen Security-Settings in eine Datei, welche mit dem Modul SecuritySettingsQuery ausgelesen werden kann.", + ModuleAlias: []string{"dumpsecuritysettings"}, + ModuleCompatibility: []string{"windows"}, + RequiresElevatedPrivileges: true, + InputParams: ParameterSyntaxMap{ + "path": ParameterSyntax{ + ParamName: "path", + IsOptional: true, + ParamDescription: "Hier kann ein Pfad angegeben werden, an welchem die zu dumpende Datei abgelegt werden soll. Wird kein Pfad angegeben, wird sie in einem Temporären Ordner abgelegt. Unabhängig davon, wird sie so oder so in die Modul-Artefakte und somit den Output aufgenommen. Wird hier ein Pfad angegeben, muss dieser auch im Query-Modul explizit angegeben werden.", + }, + }, + } +} + +func (mh *MethodHandler) DumpSecuritySettings(params ParameterMap) (r ModuleResult) { + var path string + var err error + + // Pfad bestimmen + if params["path"] != "" { + path, err = util.GetAbsolutePath(params["path"] + "secedit.cfg") + } else { + if !util.IsDir(params["path"]) { + r.Err = errors.New("Der angegebene Pfad ist ungültig.") + return + } + path = static.TempPath + static.PATH_SEPERATOR + "secedit.cfg" + } + + if err != nil { + r.Err = err + } + + // secedit.cfg Datei generieren + exportErr, err := util.ExecCommand("secedit /export /cfg \"" + path + "\" ") + if err != nil { + r.Err = err + return + } + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "file", + Value: path, + IsFile: true, + }) + + if !util.IsFile(path) { + r.Err = errors.New("Beim Exportieren der Security-Policy-Datei ist möglicherweise ein Fehler aufgetreten:\n" + exportErr) + return + } + + r.Result = "Die Security-Policy-Datei wurde exportiert." + return +} diff --git a/modules/win_export_installed_software.go b/modules/win_export_installed_software.go new file mode 100644 index 0000000..84eacc9 --- /dev/null +++ b/modules/win_export_installed_software.go @@ -0,0 +1,128 @@ +// +build windows + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "github.com/pkg/errors" + "golang.org/x/sys/windows/registry" + s "strings" +) + +func (mh *MethodHandler) ExportInstalledSoftwareInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "ExportInstalledSoftware", + ModuleDescription: "ExportInstalledSoftware speichert alle Namen und Versionsnummern von installierter Software in einer CSV Datei ab.", + ModuleAlias: []string{"exportinstalledsoftware"}, + ModuleCompatibility: []string{"windows"}, + InputParams: ParameterSyntaxMap{ + "path": ParameterSyntax{ + ParamName: "path", + IsOptional: true, + ParamDescription: "Hier kann ein Pfad angegeben werden, an welchem die CSV Datei abgelegt werden soll. Wird kein Pfad angegeben, wird sie in einem Temporären Ordner abgelegt. Unabhängig davon, wird sie so oder so in die Modul-Artefakte und somit den Output aufgenommen.", + }, + }, + } +} + +func (mh *MethodHandler) ExportInstalledSoftware(params ParameterMap) (r ModuleResult) { + keySlice := []string{ + // x64 + `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`, + // x32 + `HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall`, + } + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "x64-registry-path", + Value: keySlice[0], + }) + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "x32-registry-path", + Value: keySlice[1], + }) + + separator := ";" + var nameVersionSlice []string + + for n := range keySlice { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, s.Replace(keySlice[n], `HKEY_LOCAL_MACHINE\`, "", 1), registry.ENUMERATE_SUB_KEYS) + if err != nil { + r.Err = err + return + } + + // Alle Name der SubKeys auslesen + subKeys, err := key.ReadSubKeyNames(-1) + if err != nil { + r.Err = err + return + } + + // Durch alle SubKeys iterieren + for _, subKeyName := range subKeys { + // Wert der DisplayName Value auslesen + displayName, err := util.RegQuery(keySlice[n]+"\\"+subKeyName, "DisplayName") + if err != nil { + if err != static.ERROR_VALUE_NOT_FOUND { + r.Err = err + return + } + } + + // Wert der DisplayVersion Value auslesen + displayVersion, err := util.RegQuery(keySlice[n]+"\\"+subKeyName, "DisplayVersion") + if err != nil { + if err != static.ERROR_VALUE_NOT_FOUND { + r.Err = err + return + } + } + + /* + // Leere Einträge nicht berücksichtigen + if displayName != "" && displayVersion != "" { + nameVersionSlice = append(nameVersionSlice, displayName + separator + displayVersion) + } + */ + nameVersionSlice = append(nameVersionSlice, displayName+separator+displayVersion) + } + err = key.Close() + if err != nil { + logger.Err(errors.Wrap(err, "Fehler in ExportInstalledSoftware").Error()) + } + } + + // nameVersionSlice als CSV exportieren + var path string + var err error + + if params["path"] != "" { + path, err = util.GetAbsolutePath(params["path"] + "InstalledSoftware.csv") + } else { + path = static.TempPath + static.PATH_SEPERATOR + "InstalledSoftware.csv" + } + + if err != nil { + r.Err = err + } + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "file", + Value: path, + IsFile: true, + }) + + err = util.CreateFile(nameVersionSlice, path) + if err != nil { + r.Err = err + return + } + + r.Result = "Die CSV Datei wurde erfolgreich gespeichert!" + return +} diff --git a/modules/win_get_account_name.go b/modules/win_get_account_name.go new file mode 100644 index 0000000..3280a4f --- /dev/null +++ b/modules/win_get_account_name.go @@ -0,0 +1,85 @@ +// +build windows + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" + s "strings" +) + +func (mh *MethodHandler) GetAccountNameInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "GetAccountName", + ModuleDescription: "GetAccountName gibt den Name des Gast oder Lokalen Accounts aus.", + ModuleAlias: []string{"getaccountname"}, + ModuleCompatibility: []string{"windows"}, + InputParams: ParameterSyntaxMap{ + "type": ParameterSyntax{ + ParamName: "type", + ParamAlias: []string{"typ"}, + ParamDescription: "Accounttyp dessen Name abgefragt werden möchte. (Guest/User)", + }, + }, + } +} + +func (mh *MethodHandler) GetAccountName(params ParameterMap) (r ModuleResult) { + switch s.ToLower(params["type"]) { + case "guest": + // Liste aller Nutzer mit Name und SID + allUsers, err := util.ExecCommand("Get-WmiObject -Class Win32_UserAccount -Filter LocalAccount='True' | Select-Object Name, SID") + if err != nil { + r.Err = err + return + } + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "Get-WmiObject -Class Win32_UserAccount -Filter LocalAccount='True' | Select-Object Name, SID", + Value: "Liste aller Nutzer mit Name und SID:\n" + allUsers, + }) + + r.ResultRaw = allUsers + + lineSlice := s.Split(allUsers, "\n") + for n := range lineSlice { + if s.Contains(lineSlice[n], " ") { + line := s.TrimSpace(lineSlice[n]) + + back := s.TrimSpace(line[s.LastIndex(line, " "):]) + front := s.TrimSpace(line[:s.LastIndex(line, " ")]) + // Das Konto, welches auf 501 endet ist das Gast Konto + if s.HasSuffix(back, "501") { + r.Result = front + } + } + } + + case "user": + // Name des akutuellen lokalen Kontos mit hilfe von whoami finden + username, err := util.ExecCommand("cmd.exe /c \"echo %USERNAME%\"") + + if err != nil { + r.Err = err + return + } + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "cmd.exe /c \"echo %USERNAME%\"", + Value: "Name des aktuellen Users:\n" + username, + }) + + r.ResultRaw = username + + r.Result = s.TrimSpace(username) + } + return +} + +func (mh *MethodHandler) GetAccountNameValidate(params ParameterMap) error { + if s.ToLower(params["typ"]) != "guest" && s.ToLower(params["typ"]) != "user" { + return errors.New("Der Accounttyp ist falsch geschrieben oder wird nicht unterstützt. Bitte 'Guest' oder 'User' verwenden.") + } + return nil +} diff --git a/modules/win_get_user_sid.go b/modules/win_get_user_sid.go new file mode 100644 index 0000000..e03565d --- /dev/null +++ b/modules/win_get_user_sid.go @@ -0,0 +1,52 @@ +// +build windows + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" + s "strings" +) + +func (mh *MethodHandler) GetUserSIDInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "GetUserSID", + ModuleDescription: "GetUserSID gibt die SID des angegebenen Nutzer aus.", + ModuleAlias: []string{"getusersid"}, + ModuleCompatibility: []string{"windows"}, + InputParams: ParameterSyntaxMap{ + "userName": ParameterSyntax{ + ParamName: "userName", + ParamAlias: []string{}, + ParamDescription: "Windows Nutzername dessen SID bestimmt werden soll.", + }, + }, + } +} + +func (mh *MethodHandler) GetUserSID(params ParameterMap) (r ModuleResult) { + // Ausgabe der SID + sid, err := util.ExecCommand("cmd.exe /c \"wmic useraccount where name='" + params["userName"] + "' get sid\"") + + if err != nil { + r.Err = err + return + } + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "cmd.exe /c \"wmic useraccount where name='" + params["userName"] + "' get sid\"", + Value: sid, + }) + + r.ResultRaw = sid + + // Prüfen ob ein Fehler aufgetreten ist + if !s.Contains(sid, "SID") { + r.Err = errors.New("Der Nutzername ist nicht gültig.") + return + } + + r.Result = s.TrimSpace(sid[s.LastIndex(sid, "S-"):]) + return +} diff --git a/modules/win_get_win_env.go b/modules/win_get_win_env.go new file mode 100644 index 0000000..9082657 --- /dev/null +++ b/modules/win_get_win_env.go @@ -0,0 +1,51 @@ +// +build windows + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" + s "strings" +) + +func (mh *MethodHandler) GetWinEnvInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "GetWinEnv", + ModuleDescription: "GetWinEnv gibt den Wert der angegebenen Windows Umgebungsvariable aus.", + ModuleAlias: []string{}, + ModuleCompatibility: []string{"windows"}, + InputParams: ParameterSyntaxMap{ + "envVar": ParameterSyntax{ + ParamName: "envVar", + ParamAlias: []string{}, + ParamDescription: "Name der Windows-Umgebungsvariable.", + }, + }, + } +} + +func (mh *MethodHandler) GetWinEnv(params ParameterMap) (r ModuleResult) { + envVar, err := util.ExecCommand("cmd.exe /c \"echo %" + params["envVar"] + "%\"") + + if err != nil { + r.Err = err + return + } + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "cmd.exe /c \"echo %" + params["envVar"] + "%\"", + Value: envVar, + }) + + r.ResultRaw = envVar + + // Prüfen ob ein Fehler aufgetreten ist + if s.TrimSpace(envVar) == params["envVar"] { + r.Err = errors.New("Bei abfragen der Windows Umgebungsvariable '" + params["envVar"] + "'ist ein Fehler aufgetreten.") + return + } + + r.Result = s.TrimSpace(envVar) + return +} diff --git a/modules/win_is_gp_template_present.go b/modules/win_is_gp_template_present.go new file mode 100644 index 0000000..1d5e909 --- /dev/null +++ b/modules/win_is_gp_template_present.go @@ -0,0 +1,83 @@ +// +build windows + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/pkg/errors" + "os" + s "strings" +) + +func (mh *MethodHandler) IsGPTemplatePresentInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "IsGPTemplatePresent", + ModuleDescription: "IsGPTemplatePresent prüft ob das angegebene Group Policy Administrative Template vorhanden ist.", + ModuleAlias: []string{"isgptemplatetresent"}, + ModuleCompatibility: []string{"windows"}, + InputParams: ParameterSyntaxMap{ + "templateName": ParameterSyntax{ + ParamName: "templateName", + ParamAlias: []string{}, + ParamDescription: "Vollständiger Dateinamen des Templates z.B.: AdmPwd.admx/adml", + }, + }, + } +} + +// Prüfen ob angegebene .admx/.adml File vorhanden ist +func (mh *MethodHandler) IsGPTemplatePresent(params ParameterMap) (r ModuleResult) { + + // Format von templateName prüfen + admx und adml Dateipfad generieren + admx := "" + adml := "" + if s.Contains(params["templateName"], "/") { + filePathSlice := s.Split(params["templateName"], "/") + admx = filePathSlice[0] + adml = `\` + s.ReplaceAll(admx, ".admx", ".adml") + } else { + r.Err = errors.New("Der angegebene Templatename ist nicht im Format 'Name.admx/adml'.") + return + } + + // Ordnernamen der adml Dateien in allen Sprachen + languages := []string{ + "de-DE", + "en-US", + "cs-CZ", + "da-DK", + "el-GR", + "es-ES", + "fi-FI", + "fr-FR", + "hu-HU", + "it-IT", + "ja-JP", + "ko-KR", + "nb-NO", + "nl-NL", + "pl-PL", + "pt-BR", + "pt-PT", + "ru-RU", + "sv-SE", + "tr-TR", + "zh-CN", + "zh-TW", + } + + // !!!Achtung!!! Abhäning von verwendeter Sprache. + // Prüfen ob die admx und adml Dateien vorhanden sind + if util.IsFile(os.Getenv("windir") + `\policyDefinitions\` + admx) { + for n := range languages { + if util.IsFile(os.Getenv("windir") + `\policyDefinitions\` + languages[n] + adml) { + r.Result = "true" + return + } + } + } + + r.Result = "false" + return +} diff --git a/modules/win_registry_query.go b/modules/win_registry_query.go new file mode 100644 index 0000000..b8f7cc4 --- /dev/null +++ b/modules/win_registry_query.go @@ -0,0 +1,55 @@ +// +build windows + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" +) + +func (mh *MethodHandler) RegistryQueryInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "RegistryQuery", + ModuleAlias: []string{"registryquery", "regquery", "registry"}, + ModuleDescription: "RegistryQuery gibt die Value eines Registry Keys aus.", + ModuleCompatibility: []string{"windows"}, + InputParams: ParameterSyntaxMap{ + "key": ParameterSyntax{ + ParamName: "key", + ParamDescription: "Vollständiger Key z.B.: HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies...", + }, + "value": ParameterSyntax{ + ParamName: "value", + ParamDescription: "Value des Registry Keys.", + }, + }, + } +} + +func (mh *MethodHandler) RegistryQuery(params ParameterMap) (r ModuleResult) { + res, err := util.RegQuery(params["key"], params["value"]) + + if err != nil { + switch { + case err == static.ERROR_VALUE_NOT_FOUND: + r.ResultRaw = err.Error() + r.Result = "valueNonExistent" + case err == static.ERROR_KEY_NOT_FOUND: + r.ResultRaw = err.Error() + r.Result = "keyNonExistent" + default: + r.Err = err + } + } else { + + r.Artifacts = append(r.Artifacts, Artifact{ + Name: "Key: " + params["key"] + " | Value: " + params["value"], + Value: res, + }) + + r.ResultRaw = res + r.Result = r.ResultRaw + } + return +} diff --git a/modules/win_security_settings_query.go b/modules/win_security_settings_query.go new file mode 100644 index 0000000..aa9a5c9 --- /dev/null +++ b/modules/win_security_settings_query.go @@ -0,0 +1,66 @@ +// +build windows + +package modules + +import ( + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "path/filepath" + s "strings" +) + +func (mh *MethodHandler) SecuritySettingsQueryInit() ModuleSyntax { + return ModuleSyntax{ + ModuleName: "SecuritySettingsQuery", + ModuleDescription: "SecuritySettingsQuery ermöglicht verschiedene Bereiche der Security Settings auszulesen, die mit RegistryQuery nicht auslesbar sind.", + ModuleCompatibility: []string{"windows"}, + ModuleAlias: []string{"securitysettingsquery"}, + RequiresElevatedPrivileges: true, + InputParams: ParameterSyntaxMap{ + "valueName": ParameterSyntax{ + ParamName: "valueName", + ParamDescription: "ValueName der Security Setting. Bitte im Benutzerhandbuch nachschlagen.", + }, + "path": ParameterSyntax{ + ParamName: "path", + ParamDescription: "Pfad zur Dump-Datei. Muss nur angegeben werden, wenn im Dump-Modul ein Pfad spezifiziert wurde.", + IsOptional: true, + }, + }, + } +} + +func (mh *MethodHandler) SecuritySettingsQuery(params ParameterMap) (r ModuleResult) { + if params["path"] == "" { + params["path"] = static.TempPath + static.PATH_SEPERATOR + "secedit.cfg" + } else { + // Wenn nur der Pfad zu einem Ordner angegeben ist wird der Dateiname ergänzt + if util.IsDir(params["path"]) { + params["path"] = filepath.Clean(params["path"] + static.PATH_SEPERATOR + "secedit.cfg") + } + } + + lines, err := util.ReadUTF16File(params["path"]) + + if err != nil { + r.Err = err + return + } + + // Im Stringslice nach Value suchen z.B.: MinimumPasswordAge + for _, line := range lines { + if s.Contains(line, params["valueName"]) { + line = s.ReplaceAll(line, " ", "") + line = s.ReplaceAll(line, "\"", "") + + r.ResultRaw = s.Split(line, "=")[1] + r.Result = r.ResultRaw + } + } + + if r.Result == "" { + r.Result = "keyNonExistent" + } + return +} diff --git a/static/static.go b/static/static.go new file mode 100644 index 0000000..4d88fe6 --- /dev/null +++ b/static/static.go @@ -0,0 +1,178 @@ +// Im Package static werden Konstanten und "Konstanten-ähnliche" Variablen gesammelt. Dies erlaubt das leichte anpassen +// vieler Konstanten und dient der Übersichtlichkeit. +package static + +import ( + "github.com/pkg/errors" + "github.com/schollz/progressbar/v3" + "os" +) + +var ( + // Die Liste der verfügbaren Module. Wird ein neues Modul implementiert, muss es hier hinzugefügt werden. + Modules = []string{ + // All + "ExecuteCommand", + "FileContent", + "IsFile", + "Grep", + "Permissions", + "Script", + // Linux + "Auditctl", + "AwkScript", + "BashScript", + "CheckPartition", + "Modprobe", + "NftListRuleset", + "Sshd", + "Stat", + "Sysctl", + "Systemctl", + // Linux / Darwin + "IsInstalled", + "IsNotInstalled", + // Redhat + "Authselect", + // Windows + "AuditPolicyQuery", + "DumpSecuritySettings", + "ExportInstalledSoftware", + "GetAccountName", + "GetUserSID", + "GetWinEnv", + "IsGPTemplatePresent", + "RegistryQuery", + "SecuritySettingsQuery", + } + + // Legt alle Betriebssysteme fest, die sich in der Linux-Wildcard befinden. + Linux = []string{ + "ubuntu", + "debian", + "centos", + "rhel", // RedHat + "sles", // Suse + "sles_sap", // Suse SAP + } + + // Legt alle Betriebssysteme fest, die sich in der Windows-Wildcard befinden. + Windows = []string{ + "windows10", + "windowsserver2012", + "windowsserver2016", + "windowsserver2019", + "windows8", + "windows7", + "windowsxp", + } + + // Legt alle Betriebssysteme fest, die sich in der Darwin-Wildcard befinden. + Darwin = []string{ + "macos11.0", + "macos10.15", + "macos10.14", + } + + // Über diesen String wird in der Jungbusch-Auditorium Global auf das Ergebnis des OS-Detectors zugegriffen. + OperatingSystem = "" + + // Über diesen String wird in der Jungbusch-Auditorium Global auf den Pfad des + // temporären Ordners zugegriffen. (Nur Windows) + TempPath = "" + + // In diesem Bool wird gespeichert, ob das Jungbusch-Auditorium root, bzw. Administrator-Rechte hat. + HasElevatedPrivileges = false + + // In dieser Variable wird gespeichert, mit welchen Permissions das Jungbusch-Auditorium Dateien erstellt. + // Dieser Wert ändert sich, wenn das Auditorium Root-Rechte hat. Siehe: privilege.HasRootPrivileges + CREATE_FILE_PERMISSIONS os.FileMode = 0644 + + // In dieser Variable wird gespeichert, mit welchen Permissions das Jungbusch-Auditorium Ordner erstellt. + // Dieser Wert ändert sich, wenn das Auditorium Root-Rechte hat. Siehe: privilege.HasRootPrivileges + CREATE_DIRECTORY_PERMISSIONS os.FileMode = 0755 + + // Speichert den Error, der geworfen wird, wenn ein Registry-Key nicht gefunden wurde. (Windows) + ERROR_KEY_NOT_FOUND = errors.New("Der angegebene Key konnte nicht gefunden werden!") + + // Speichert den Error, der geworfen wird, wenn eine Registry-Value nicht gefunden wurde. (Windows) + ERROR_VALUE_NOT_FOUND = errors.New("Die angegebene Value konnte nicht gefunden werden!") + + ProgressBar *progressbar.ProgressBar +) + +const ( + CURRENT_VERSION = "V1.0 (Letzte Änderung: 29.06.2021)" + PATH_SEPERATOR = string(os.PathSeparator) + + // Setzt Default-Werte der Programmkonfiguration + EXPECTED_CONFIG_PATH string = `./config.ini` + EXPECTED_AUDIT_PATH string = `./audit.jba` + DEFAULT_OUTPUT_PATH string = `./` + DEFAULT_LOG_VERBOSITY int = 3 + DEFAULT_CONSOLE_VERBOSITY int = 2 + + // Legt fest, welche Methodennamen das Jungbusch-Auditorium in Modulen erwartet. + // Werden diese Werte angepasst, müssen auch alle bestehenden Module angepasst werden. + MODULE_INITIALIZER_SUFFIX = "Init" + MODULE_EXECUTOR_SUFFIX = "" + MODULE_VALIDATOR_SUFFIX = "Validate" + + // Legt den Name der zu erstellenden Log-Datei fest. + LOG_NAME = "Jungbusch-Auditorium" + + // Legt den Name des zu erstellenden Output-Ordners fest. + OUTPUT_FOLDER_NAME = "jba-result" + OUTPUT_TIMESTAMP_FORMAT = "02-01-06T150405" // Siehe Time.Format + + // Legt fest, welche Strings von den Modulen als Anzeige des Fortschritts ausgegeben werden + PASSED_OUTPUT = "Test '%v' PASSED" + NOTPASSED_OUTPUT = "Test '%v' NOT PASSED" + UNSUCCESSFUL_OUTPUT = "Test '%v' UNSUCCESSFUL" + NOTEXECUTED_OUTPUT = "Test '%v' not executed" + + // Reg-Ex + REG_VARIABLE_NAME = "^[A-Za-z0-9_]+$" // Zeichensatz der in Variablen erlaubt ist + REG_VARIABLE = "%([a-zA-Z0-9_]*?)%" // Anhand von diesem Zeichnsatz werden Variablen identifiziert + REG_MODULE_NAME = "^[A-Za-z0-9_]+$" // Zeichensatz der in Modul-Namen/Parameter-Namen oder Aliasen enthalten sein darf + + // Parser Errors + COMMENTBLOCK_NEVER_CLOSED = "Ein Kommentarblock wurde nicht ordnungsgemäß geschlossen." + AUDIT_CONFIG_EMPTY = "Die angegebene Audit-Konfigurationsdatei ist leer." + INVALID_LINE = "Die Zeile ist ungültig. Möglicher Grund: " + MODULE_MISSING_OPENING_BRACKET = "Die öffnende geschweifte Klammer fehlt." + MODULE_MISSING_CLOSING_BRACKET = "Die schließende geschweifte Klammer fehlt." + MODULE_MISSING_CLOSING_BRACKET_COMMA = "Die schließende geschweifte Klammer ist ungültig. Möglicherweise fehlt das Komma." + MODULE_EMPTY = "Der Audit-Schritt ist leer." + MODULE_INVALID_EXPRESSION = "Ungültiger Ausdruck." + VARIABLE_INVALID = "Der Syntax der Variable ist nicht gültig. Möglicher Grund: " + VARIABLE_INVALID_NAME = "Der Name der Variable ist nicht gültig. Möglicher Grund: " + VARIABLE_INVALID_VALUE = "Der Syntax des Werts der Variable ist nicht gültig." + MODULE_INVALID_MODULE_NAME = "Der Name des Moduls ist nicht gültig. Möglicher Grund: " + MODULE_VALUE_INVALID_MULTILINE = "Der Wert des Multiline-Parameters ist nicht gültig. Möglicher Grund: " + MODULE_VALUE_INVALID = "Der Wert des Parameters ist nicht gültig. Möglicher Grund: " + MODULE_VALUE_ALREADY_SET = "Dieser Wert darf pro Audit-Schritt nur einmalig gesetzt werden." + MODULE_MISSING_PARAMETER = "Einem Audit-Schritt fehlen folgende zwingend zu setzenden Parameter: " + MODULE_NOT_FOUND = "Das angegebene Modul wurde nicht gefunden. Mögliche Gründe: Das Modul wurde nicht als kompatibel markiert oder das Modul ist nicht mit der aktuellen Architektur kompatibel." + MODULE_REQUIRED_MODULE_PARAMETER_NOT_SET = "Folgender Modul-Parameter muss zwingend angegeben werden: " + MODULE_NO_TEXT_BEHIND_MULTILINE = "Hinter den schließenden Backticks von Multiline-Parametern ist kein Text (Kommentare o.ä.) erlaubt." + PARAMETER_EMPTY = "Der Wert des Parameters ist leer." + MODULE_INVALID_PARAMETER = "Der Parameter ist für dieses Modul nicht definiert." + GLOBAL_MODULES_NOT_ALLOWED_NESTED = "Globale Audit-Schritte dürfen keine verschachtelten Audit-Schritte haben. In folgendem Schritt ist ein Fehler aufgetreten: (ID): " + GLOBAL_MODULE_NESTED = "Globale Audit-Schritte dürfen nicht verschachtelt sein. In folgendem Schritt ist ein Fehler aufgetreten: (ID): " + GLOBAL_VARIABLE_READONLY = "Globale Variablen dürfen nur in globalen Audit-Schritten überschrieben werden. In folgendem Schritt ist ein Fehler aufgetreten (ID): " + + // Parser Errors: Gründe + TOO_MANY_QUOTATIONMARKS = "Zu viele Anführungszeichen." + TOO_MANY_TICKS = "Zu viele Backticks." + MISSING_TICKS = "Fehlende Backticks." + MISSING_QUOTATIONMARKS = "Fehlende Anführungszeichen." + VARIABLE_MISSING_PERCENTAGE = "Der Variablenname muss von Prozentzeichen umschlossen sein." + INVALID_CHARACTERS = "Es wurden nicht-erlaubte Zeichen verwendet." + INVALID_SYNTAX_OR_MISSING_QUOTATIONMARKS = "Der Syntax der Bedingung ist nicht valide oder fehlende Anführungszeichen." + INVALID_VALUE_FOR_BOOL = "Der Boolean-Wert ist ungültig. (true/false)" + ID_ALREADY_USED = "IDs müssen einzigartig sein." + ENVIRONMENT_VARIABLES_READONLY = "Umgebungsvariablen dürfen nicht überschrieben werden." + VARIABLE_ALREADY_SET = "Diese Variable wurde in diesem Modul bereits gesetzt." + ROW_IS_END_OF_MODULE = " Anmerkung: Die angegebene Zeile ist die schließende Klammer des betreffenden Moduls." +) diff --git a/test/auditconfig_test/audit_interpreter_test.go b/test/auditconfig_test/audit_interpreter_test.go new file mode 100644 index 0000000..9aed61e --- /dev/null +++ b/test/auditconfig_test/audit_interpreter_test.go @@ -0,0 +1,128 @@ +package auditconfig_test + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/interpreter" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "testing" +) + +func initVarMap() VariableMap { + varSlice := make(VariableMap) + varSlice["%result%"] = Variable{Name: "%result%", IsEnv: true} + varSlice["%passed%"] = Variable{Name: "%passed%", IsEnv: true} + varSlice["%unsuccessful%"] = Variable{Name: "%unsuccessful%", IsEnv: true} + varSlice["%os%"] = Variable{Name: "%os%", Value: static.OperatingSystem, IsEnv: true} + varSlice["%currentmodule%"] = Variable{Name: "%currentmodule%", IsEnv: true} + return varSlice +} + +func TestInterpreterBasicPassed(t *testing.T) { + _ = initMe() + + myModule := AuditModule{ + ModuleName: "ExecuteCommand", + StepID: "1", + Passed: "%result%.startsWith(\"Te\")", + Variables: initVarMap(), + ModuleParameters: ParameterMap{"command": "echo Test"}, + } + + report, err := interpreter.InterpretAudit([]AuditModule{myModule}, 1, false) + + if err != nil { + t.Errorf("Fehler aufgetreten: " + err.Error()) + } + + if report[0].Result != "PASSED" { + t.Errorf("Result: " + report[0].Result + "\nSoll: PASSED") + } + + t.Log("Success") +} + +func TestInterpreterBasicNotPassed(t *testing.T) { + _ = initMe() + + myModule := AuditModule{ + ModuleName: "ExecuteCommand", + StepID: "1", + Passed: "%result%.startsWith(\"Hallo\")", + Variables: initVarMap(), + ModuleParameters: ParameterMap{"command": "echo Test"}, + } + + report, err := interpreter.InterpretAudit([]AuditModule{myModule}, 1, false) + + if err != nil { + t.Errorf("Fehler aufgetreten: " + err.Error()) + } + + if report[0].Result != "NOTPASSED" { + t.Errorf("Result: " + report[0].Result + "\nSoll: NOTPASSED") + } + + t.Log("Success") +} + +func TestInterpreterBasicUnsuccessful(t *testing.T) { + _ = initMe() + + myModule := AuditModule{ + ModuleName: "FileContent", + StepID: "1", + Variables: initVarMap(), + ModuleParameters: ParameterMap{"file": "..."}, + } + + report, err := interpreter.InterpretAudit([]AuditModule{myModule}, 1, false) + + if err != nil { + t.Errorf("Fehler aufgetreten: " + err.Error()) + } + + if report[0].Result != "UNSUCCESSFUL" { + t.Errorf("Result: " + report[0].Result + "\nSoll: UNSUCCESSFUL") + } + + t.Log("Success") +} + +func TestInterpreterConditionNotPassed(t *testing.T) { + _ = initMe() + + myModule := AuditModule{ + Condition: "false", + } + + report, err := interpreter.InterpretAudit([]AuditModule{myModule}, 1, false) + + if err != nil { + t.Errorf("Fehler aufgetreten: " + err.Error()) + } + + if report[0].Result != "NOTEXECUTED" { + t.Errorf("Result: " + report[0].Result + "\nSoll: NOTPASSED") + } + + t.Log("Success") +} + +func TestInterpreterUndeclaredVariable(t *testing.T) { + _ = initMe() + + myModule := AuditModule{ + ModuleName: "FileContent", + StepID: "1", + Variables: VariableMap{}, + ModuleParameters: ParameterMap{"file": "%result%"}, + } + + _, err := interpreter.InterpretAudit([]AuditModule{myModule}, 1, false) + + if err == nil { + t.Errorf("Nicht deklarierte Variablen wurden nicht abgefangen.") + } + + t.Log("Success") +} diff --git a/test/auditconfig_test/audit_parser_test.go b/test/auditconfig_test/audit_parser_test.go new file mode 100644 index 0000000..490b110 --- /dev/null +++ b/test/auditconfig_test/audit_parser_test.go @@ -0,0 +1,852 @@ +package auditconfig_test + +import ( + "fmt" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/parser" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/syntaxchecker" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/modulecontroller" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + s "strings" + "testing" +) + +// +// +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// Positive Tests +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// +// + +func TestParseAuditConfigurationGlobalValid(t *testing.T) { + loadedModules := initMe() + + t.Run("TestGlobalVariable", func(t *testing.T) { + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/global.jba") + checkMe(t, err) + fmt.Println(err) + if !m[0].IsGlobal { + t.Errorf("Das Modul sollte global sein, ist es aber nicht!") + } + }) + t.Run("TestMulitpleGlobalVariables", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_global.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestNonGlobalUseGlobal", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/non_global_use_global.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationCaseSensitivity(t *testing.T) { + loadedModules := initMe() + + t.Run("TestVariableCaseSenitivity1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/case_sensitivity1.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestVariableCaseSenitivity2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/case_sensitivity2.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestVariableCaseSenitivityScript1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/case_sensitivity_script1.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestVariableCaseSenitivityScript2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/case_sensitivity_script2.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationAlias(t *testing.T) { + loadedModules := initMe() + + t.Run("TestNameAlias", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/name_alias.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestParamAlias", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/param_alias.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestIfAlias", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/if_alias.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationRequireselevatedprivilegesValid(t *testing.T) { + loadedModules := initMe() + + t.Run("TestRequireselevatedprivilegesTrue1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/requireselevatedprivileges_true1.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestRequireselevatedprivilegesTrue2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/requireselevatedprivileges_true2.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestRequireselevatedprivilegesTrue3", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/requireselevatedprivileges_true3.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestRequireselevatedprivilegesFalse1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/requireselevatedprivileges_false1.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestRequireselevatedprivilegesFalse2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/requireselevatedprivileges_false2.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestRequireselevatedprivilegesFalse3", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/requireselevatedprivileges_false3.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationPrint(t *testing.T) { + loadedModules := initMe() + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/print.jba") + checkMe(t, err) + fmt.Println(err) +} + +func TestParseAuditConfigurationDescription(t *testing.T) { + loadedModules := initMe() + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/description.jba") + checkMe(t, err) + fmt.Println(err) +} + +func TestParseAuditConfigurationBackticks(t *testing.T) { + loadedModules := initMe() + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/backticks.jba") + checkMe(t, err) + fmt.Println(err) +} + +func TestParseAuditConfigurationMultilineValid(t *testing.T) { + loadedModules := initMe() + + t.Run("TestMultiValid", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multi_valid.jba") + checkMe(t, err) + + }) + t.Run("TestMultiValidOneLine", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multi_valid_one_line.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationBasic(t *testing.T) { + loadedModules := initMe() + + t.Run("TestBasicModule", func(t *testing.T) { + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/basic_module.jba") + checkMe(t, err) + fmt.Println(err) + + n := recursivelyTestNestedModules(m, t, 0) + if n != 1 { + t.Errorf("Falsche Anzahl Module: %v gefunden, sollte 1 sein", n) + } + }) + t.Run("TestBasicModuleNested", func(t *testing.T) { + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/basic_module_nested.jba") + checkMe(t, err) + fmt.Println(err) + + n := recursivelyTestNestedModules(m, t, 0) + if n != 2 { + t.Errorf("Falsche Anzahl Module: %v gefunden, sollte 2 sein", n) + } + }) + t.Run("TestMultipleBasicModules", func(t *testing.T) { + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_basic_modules.jba") + checkMe(t, err) + fmt.Println(err) + + n := recursivelyTestNestedModules(m, t, 0) + if n != 3 { + t.Errorf("Falsche Anzahl Module: %v gefunden, sollte 3 sein", n) + } + }) + t.Run("TestMultipleBasicModulesNested", func(t *testing.T) { + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_basic_modules_nested.jba") + checkMe(t, err) + fmt.Println(err) + + n := recursivelyTestNestedModules(m, t, 0) + if n != 10 { + t.Errorf("Falsche Anzahl Module: %v gefunden, sollte 10 sein", n) + } + }) +} + +func TestParseAuditConfigurationWhitespace(t *testing.T) { + loadedModules := initMe() + + t.Run("TestLeadingWhitespace", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/leading_whitespace.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestEmptyLines", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/empty_lines.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationKeywordSequence(t *testing.T) { + loadedModules := initMe() + + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/keyword_sequence.jba") + checkMe(t, err) + fmt.Println(err) +} + +func TestParseAuditConfigurationMissingCondition(t *testing.T) { + loadedModules := initMe() + + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_condition.jba") + checkMe(t, err) + fmt.Println(err) +} + +func TestParseAuditConfigurationSymbolInVariable(t *testing.T) { + loadedModules := initMe() + + t.Run("TestColonInValue", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/colon_in_value.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestBraceInValue", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/brace_in_value.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestRoundBracketInValue", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/round_bracket_in_value.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationSymbolInParam(t *testing.T) { + loadedModules := initMe() + + t.Run("TestEqualSignInParam", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/equal_sign_in_param.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestBraceInParam", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/brace_in_param.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestRoundBracketInParam", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/round_bracket_in_param.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationSlashesAndCommentsInParams(t *testing.T) { + loadedModules := initMe() + + t.Run("TestSlashesInParam", func(t *testing.T) { + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/slashes_in_param.jba") + checkMe(t, err) + fmt.Println(err) + if m[0].ModuleParameters["command"] != "te//st" { + t.Errorf("Parameter wurde falsch geparsed") + } + }) + t.Run("TestSlashesInParamWithComment", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/slashes_in_param_with_comment.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestSlashesInParamWithQuotationMarksInComment", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/slashes_in_param_with_quotation_mark_in_comment.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestCommentAfterParam", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/comment_after_param.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestCommentAfterParamWithQuotiationMarks", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/comment_after_param_with_quotation_mark.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestEmptyParamWithComment", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/empty_param_with_comment.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestCommentAfterBrace", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/comment_after_brace.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationSlashesAndCommentsInVariables(t *testing.T) { + loadedModules := initMe() + + t.Run("TestSlashesInValue", func(t *testing.T) { + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/slashes_in_value.jba") + checkMe(t, err) + fmt.Println(err) + if m[0].Variables["%test%"].Value != "te//st" { + t.Errorf("Variable wurde falsch geparsed") + } + }) + t.Run("TestSlashesInValueWithComment", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/slashes_in_value_with_comment.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestSlashesInValueWithQuotationMarksInComment", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/slashes_in_value_with_quotation_mark_in_comment.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestCommentAfterValue", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/comment_after_value.jba") + checkMe(t, err) + fmt.Println(err) + }) + t.Run("TestCommentAfterValueWithQuotiationMarks", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/comment_after_value_with_quotation_mark.jba") + checkMe(t, err) + fmt.Println(err) + }) +} + +// +// +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// Negative Tests +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// +// + +func TestParseAuditConfigurationGlobalInvalid(t *testing.T) { + loadedModules := initMe() + + t.Run("TestGlobalWrongOrder", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/global_wrong_order.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleGlobalWrongOrder1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_global_wrong_order1.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleGlobalWrongOrder2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_global_wrong_order2.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestNestedGlobal1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/nested_global1.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestNestedGlobal2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/nested_global2.jba") + checkMeNegated(t, err) + }) + t.Run("TestGlobalOverwrite", func(t *testing.T) { + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/global_overwrite.jba") + fmt.Println(m[1].IsGlobal) + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationAliasNeg(t *testing.T) { + loadedModules := initMe() + + t.Run("TestAdditionalAlias", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/additional_alias.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationRequireselevatedprivilegesInvalid(t *testing.T) { + loadedModules := initMe() + + t.Run("TestRequireselevatedprivilegesInvalid1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/requireselevatedprivileges_invalid1.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestRequireselevatedprivilegesInvalid2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/requireselevatedprivileges_invalid2.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationPrintInvalid(t *testing.T) { + loadedModules := initMe() + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/print_invalid.jba") + checkMeNegated(t, err) + fmt.Println(err) +} + +func TestParseAuditConfigurationDescriptionInvalid(t *testing.T) { + loadedModules := initMe() + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/description_invalid.jba") + checkMeNegated(t, err) + fmt.Println(err) +} + +func TestParseAuditConfigurationMultipleParams(t *testing.T) { + loadedModules := initMe() + + t.Run("TestMultipleParams1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_params1.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleParams2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_params2.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleParams3", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_params3.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleParams4", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_params4.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleParams5", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_params5.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleParamsTrue", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_param_true.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleParamsFalse", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_param_false.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationMultilineInvalid(t *testing.T) { + loadedModules := initMe() + + t.Run("TestMultiInvalidTextAfterEnd", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multi_invalid_text_after_end.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultiInvalidOneLineAfterEnd", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multi_invalid_one_line_text_after_end.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultiInvalidQuotesInsteadOfTicks", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multi_invalid_quotes_instead_of_ticks.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultiInvalidEmpty", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multi_invalid_empty.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultiInvalidEOF", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multi_invalid_eof.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationEmptyFile(t *testing.T) { + loadedModules := initMe() + + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/empty_file.jba") + checkMeNegated(t, err) + fmt.Println(err) +} + +func TestParseAuditConfigurationEmptyModule(t *testing.T) { + loadedModules := initMe() + + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/empty_module.jba") + checkMeNegated(t, err) + fmt.Println(err) +} + +func TestParseAuditConfigurationBrace(t *testing.T) { + loadedModules := initMe() + + t.Run("TestOpeningBraceOnly", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/opening_brace_only.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingBrace", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_brace.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationWhitespaceInNames(t *testing.T) { + loadedModules := initMe() + + t.Run("TestWhitespaceInParamName", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/whitespace_in_param_name.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestWhitespaceInModuleName", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/whitespace_in_module_name.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationComma(t *testing.T) { + loadedModules := initMe() + + t.Run("TestMissingComma", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_comma.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestAdditionalComma", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/additional_comma.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationRedeclaredVariables(t *testing.T) { + loadedModules := initMe() + + t.Run("TestRedeclaredVariablesSameModule1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/redeclared_variable_same_module1.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestRedeclaredVariablesSameModule2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/redeclared_variable_same_module2.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationPercentSign(t *testing.T) { + loadedModules := initMe() + + t.Run("TestMissingBeginningPercentSign", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_beginning_percent_sign.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingEndingPercentSign", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_ending_percent_sign.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingPercentSigns", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_percent_signs.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationVariableDeclaration(t *testing.T) { + loadedModules := initMe() + + t.Run("TestMissingVariableName", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_variable_name.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingVariable", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_variable.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingEqualSign", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_equal_sign.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingVariableValue", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_variable_value.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingVariableAndEqualSign", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_variable_equal_sign.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestEqualSignOnly", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/equal_sign_only.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestVariableNameOnly", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/variable_name_only.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestInvalidVariableDeclaration1", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/invalid_variable_declaration1.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestInvalidVariableDeclaration2", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/invalid_variable_declaration2.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestInvalidVariableDeclaration3", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/invalid_variable_declaration3.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationParameterDeclaration(t *testing.T) { + loadedModules := initMe() + + t.Run("TestMissingParameter", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_parameter.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingParameterValue", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_parameter_value.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestParameterValueOnly", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/parameter_value_only.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestParameterNameOnly", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/parameter_name_only.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestInvalidParameterValue", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/invalid_parameter_value.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingColon", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_colon.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestColonOnly", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/colon_only.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingModuleName", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_module_name.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleModuleDeclaration", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_module_declaration.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationQuotationMarks(t *testing.T) { + loadedModules := initMe() + + t.Run("TestOneQuotationMarkOnly", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/one_quotation_mark_only.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingOpeningQuotationMark", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_opening_quotation_mark.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingClosingQuotationMark", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_closing_quotation_mark.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingQuotationMarks", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_quotation_marks.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationStepID(t *testing.T) { + loadedModules := initMe() + + t.Run("TestMissingStepID", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_stepid.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMissingStepIDNested", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/missing_stepid_nested.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleStepID", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_stepid.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleStepIDNested", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_stepid_nested.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) + t.Run("TestMultipleStepIDDeclaration", func(t *testing.T) { + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/multiple_stepid_declaration.jba") + checkMeNegated(t, err) + fmt.Println(err) + }) +} + +func TestParseAuditConfigurationOverwrittenEnvVariable(t *testing.T) { + loadedModules := initMe() + + _, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/parser_testdata/overwrite_env_variable.jba") + checkMeNegated(t, err) + fmt.Println(err) +} + +// +// +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// Utility +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// +// + +func executeMe(loadedModules []ModuleSyntax, file string) ([]AuditModule, bool, int, error) { + lines, err := util.ReadFile(file) + if err != nil { + panic(err) + } + + err = syntaxchecker.Syntax(lines) + fmt.Println(err) + if err != nil { + return nil, false, 0, err + } + + return parser.Parse(lines, loadedModules) +} + +func initMe() []ModuleSyntax { + cs := ConfigStruct{OutputPath: gopath + "./ProgrammOutput"} + err := logger.InitializeLogger(&cs, []LogMsg{}) + if err != nil { + panic(err) + } + static.OperatingSystem = "windows10" + loadedModules, err := modulecontroller.Initialize(false) + if err != nil { + panic(err) + } + return loadedModules +} + +func checkMe(t *testing.T, err error) { + if err != nil { + t.Errorf("Fehler beim Parsen: %v", err) + } +} + +func checkMeNegated(t *testing.T, err error) { + if err == nil { + t.Errorf("Invalider Syntax wurde angenommen.") + } else { + if s.HasPrefix(err.Error(), "Die Audit-Datei ") { + t.Errorf("Invalide Datei.") + } + } +} + +func recursivelyTestNestedModules(auditModules []AuditModule, t *testing.T, n int) int { + for i := range auditModules { + if auditModules[i].ModuleName != "ExecuteCommand" { + t.Errorf("Falscher Modul-Name: %v, sollte ExecuteCommand sein", auditModules[i].ModuleName) + } + for k, v := range auditModules[i].ModuleParameters { + if v != "test" { + t.Errorf("%v-Parameter falsch gesetzt: \"%v\" erhalten, sollte \"test\" sein", k, v) + } + } + if len(auditModules[i].NestedModules) != 0 { + n = recursivelyTestNestedModules(auditModules[i].NestedModules, t, n) + } + n++ + } + return n +} diff --git a/test/auditconfig_test/audit_validator_test.go b/test/auditconfig_test/audit_validator_test.go new file mode 100644 index 0000000..133e50c --- /dev/null +++ b/test/auditconfig_test/audit_validator_test.go @@ -0,0 +1,63 @@ +package auditconfig_test + +import ( + interpreter2 "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/interpreter" + "os" + "testing" +) + +var ( + gopath = os.Getenv("gopath") + `\src\github.com\Jungbusch-Softwareschmiede\jungbusch-auditorium\` +) + +func TestValidateParametersExistingVariable(t *testing.T) { + loadedModules := initMe() + + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/validator_testdata/existing_variable.jba") + if err != nil { + t.Errorf("Fehler im Parser: %v", err) + } + + if err := interpreter2.ValidateParameters(m); err != nil { + t.Errorf("Ungültige Datei wurde angenommen %v", err) + } +} + +func TestValidateParametersMultipleVariables(t *testing.T) { + loadedModules := initMe() + + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/validator_testdata/multiple_variables.jba") + if err != nil { + t.Errorf("Fehler im Parser: %v", err) + } + + if err := interpreter2.ValidateParameters(m); err != nil { + t.Errorf("Ungültige Datei wurde angenommen %v", err) + } +} + +func TestValidateParametersNonExistingVariable(t *testing.T) { + loadedModules := initMe() + + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/validator_testdata/non_existing_variable.jba") + if err != nil { + t.Errorf("Unbekannter Fehler: " + err.Error()) + } + + if err := interpreter2.ValidateParameters(m); err == nil { + t.Errorf("Ungültige Datei wurde angenommen %v", err) + } +} + +func TestValidateParametersMultipleNonExistingVariables(t *testing.T) { + loadedModules := initMe() + + m, _, _, err := executeMe(loadedModules, gopath+"/test/testdata/validator_testdata/multiple_non_existing_variables.jba") + if err != nil { + t.Errorf("Fehler im Parser: %v", err) + } + + if err := interpreter2.ValidateParameters(m); err == nil { + t.Errorf("Ungültige Datei wurde angenommen %v", err) + } +} diff --git a/test/config_test/cli_auditcfg_test.go b/test/config_test/cli_auditcfg_test.go new file mode 100644 index 0000000..66da4f9 --- /dev/null +++ b/test/config_test/cli_auditcfg_test.go @@ -0,0 +1,215 @@ +package config_test + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-interpreter" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-parser" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "os" + "testing" +) + +/* +Ordner: Test1 +CLI-Parameter: Keine +Audit-Datei: Wird über CLI gesetzt +Beschreibung: Die Audit-Config, welche über die CLI gesetzt wird, sollte ausgewählt werden +*/ +func TestAuditJbaCLI(t *testing.T) { + // Pfad zu dem Ordner, in welchem wir die Dateien testen wollen + os.Args = []string{gopath + `test\testdata\cli_testdata\Test1\`, `-auditConfig=./folder_with_audit/cli_audit.jba`} + cs := LoadConfig(t) + InterpretConfig(t, &cs) + Evaluate(t, &cs, gopath+`test\testdata\cli_testdata\Test1\folder_with_audit\cli_audit.jba`) +} + +/* +Ordner: Test2 +CLI-Parameter: Config wird angegeben +Audit-Datei: Wird über Config gesetzt +Beschreibung: Die Audit-Datei, die in der Config angegeben ist, soll angegeben werden +*/ +func TestAuditConfig(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test2\`, `-config=./config.ini`} + cs := LoadConfig(t) + InterpretConfig(t, &cs) + Evaluate(t, &cs, gopath+`test\testdata\cli_testdata\Test2\folder_with_audit\config_audit.jba`) +} + +/* +Ordner: Test3 +CLI-Parameter: Keine +Audit-Datei: Eine einzelne Audit mit zufälligem Name im Pfad +Beschreibung: Die einzelne Audit-Datei sollte ausgewählt werden +*/ +func TestAuditJbaSingleFileInFolder(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test3\`} + cs := LoadConfig(t) + InterpretConfig(t, &cs) + Evaluate(t, &cs, gopath+`test\testdata\cli_testdata\Test3\testauditxy.jba`) +} + +/* +Ordner: Test4 +CLI-Parameter: Keine +Audit-Datei: Audit-Datei mit Default-Name +Beschreibung: Mehrere .jba-Dateien, eine davon mit Default-Name. Diese sollte ausgewählt werden +*/ +func TestAuditJbaDefaultName(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test4\`} + cs := LoadConfig(t) + InterpretConfig(t, &cs) + Evaluate(t, &cs, gopath+`test\testdata\cli_testdata\Test4\audit.jba`) +} + +/* +Ordner: Test5 +CLI-Parameter: Keine +Audit-Datei: Keine +Beschreibung: Zwei .jba-Dateien, keine mit Default-Name in einem Ordner. Es sollte keine ausgewählt werden. +*/ +func TestAuditJbaError(t *testing.T) { + // Pfad zu dem Ordner, in welchem wir die Dateien testen wollen + os.Args = []string{gopath + `test\testdata\cli_testdata\Test5\`} + cs := LoadConfig(t) + _, err := config_interpreter.InterpretConfig(&cs) + + if err == nil { + t.Errorf("Fehlgeschlagen: Datei wurde fälschlicherweise genommen: %v", cs.AuditConfig) + } +} + +/* +Ordner: Test6 +CLI-Parameter: Keine +Audit-Datei: Keine +Beschreibung: Keine Audit-Datei im Pfad vorhanden. +*/ +func TestAuditJbaEmptyFolder(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test6\`} + cs := LoadConfig(t) + _, err := config_interpreter.InterpretConfig(&cs) + + if err == nil { + t.Errorf("Fehlgeschlagen: Datei wurde fälschlicherweise genommen: %v", cs.AuditConfig) + } +} + +/* +Ordner: Test7 +CLI-Parameter: Config wird angegeben +Audit-Datei: In Config und CLI gesetzt +Beschreibung: Eine Audit-Datei wird über das CLI und eine in der Config angegeben, die von dem CLI sollte ausgewählt werden. +*/ +func TestAuditJbaSelectionConfigCLI(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test7\`, `-auditConfig=./audit1.jba`, `-config=./config.ini`} + cs := LoadConfig(t) + InterpretConfig(t, &cs) + Evaluate(t, &cs, gopath+`test\testdata\cli_testdata\Test7\audit1.jba`) +} + +/* +Ordner: Test8 +CLI-Parameter: Keine +Audit-Datei: In CLI gesetzt +Beschreibung: Die Audit-Datei welche über das CLI angegeben wurde existiert nicht. +*/ +func TestAuditJbaNonExistentAuditCLI(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test8\`, `-auditConfig=./audit.jba`} + cs := LoadConfig(t) + _, err := config_interpreter.InterpretConfig(&cs) + + if err == nil { + t.Errorf("Fehlgeschlagen: Datei wurde fälschlicherweise genommen: %v", cs.AuditConfig) + } +} + +/* +Ordner: Test9 +CLI-Parameter: Config wird angegeben +Audit-Datei: In Config gesetzt +Beschreibung: Die Audit-Datei welche über die config angegeben wurde existiert nicht. +*/ +func TestAuditJbaNonExistentAuditConfig(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test9\`, `-config=./config.ini`} + cs := LoadConfig(t) + _, err := config_interpreter.InterpretConfig(&cs) + + if err == nil { + t.Errorf("Fehlgeschlagen: Datei wurde fälschlicherweise genommen: %v", cs.AuditConfig) + } +} + +/* +Ordner: Test10 +CLI-Parameter: Config wird angegeben +Audit-Datei: In Config gesetzt +Beschreibung: Die Audit-Datei welche über die config angegeben wurde enthält mehrere "/". +*/ +func TestAuditJbaTooManySlashes(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test10\`, `-config=./config.ini`} + cs := LoadConfig(t) + _, err := config_interpreter.InterpretConfig(&cs) + + if err == nil { + t.Errorf("Fehlgeschlagen: Datei wurde fälschlicherweise genommen: %v", cs.AuditConfig) + } +} + +/* +Ordner: Test11 +CLI-Parameter: Config wird angegeben +Audit-Datei: In Config gesetzt +Beschreibung: Die Audit-Datei welche über die config angegeben wurde existiert nicht. +*/ +func TestAuditJbaPathToDirectory(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test11\`, `-config=./config.ini`} + cs := LoadConfig(t) + _, err := config_interpreter.InterpretConfig(&cs) + + if err == nil { + t.Errorf("Fehlgeschlagen: Datei wurde fälschlicherweise genommen: %v", cs.AuditConfig) + } +} + +/* +Ordner: Test12 +CLI-Parameter: Config wird angegeben +Audit-Datei: In Config gesetzt +Beschreibung: Der angegebene Pfad enthält keine Audit.jba. +*/ +func TestAuditJbaPathWithoutAuditJBA(t *testing.T) { + os.Args = []string{gopath + `test\testdata\cli_testdata\Test12\`, `-config=./config.ini`} + cs := LoadConfig(t) + _, err := config_interpreter.InterpretConfig(&cs) + + if err == nil { + t.Errorf("Fehlgeschlagen: Datei wurde fälschlicherweise genommen: %v", cs.AuditConfig) + } +} + +// +// +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// Utility +// +// *=*=*=*=*=*=*=*=*=*=*=*= +// +// +// +func LoadConfig(t *testing.T) ConfigStruct { + config_parser.ResetFlags() + cs, log := config_parser.LoadConfig() + stringerr := GetLogErr(log) + if stringerr != "" { + t.Errorf("Error beim Parsen der Konfiguration: " + stringerr) + } + return cs +} + +func Evaluate(t *testing.T, cs *ConfigStruct, expectedPath string) { + if cs.AuditConfig != expectedPath { + t.Errorf("Fehlgeschlagen: \nExpected=%v\nActual= %v", expectedPath, cs.AuditConfig) + } +} diff --git a/test/config_test/cli_basic_param_test.go b/test/config_test/cli_basic_param_test.go new file mode 100644 index 0000000..9e384c3 --- /dev/null +++ b/test/config_test/cli_basic_param_test.go @@ -0,0 +1,363 @@ +package config_test + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-interpreter" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-parser" + "os" + "path/filepath" + "strconv" + "testing" +) + +func TestAuditConfigFlag(t *testing.T) { + config_parser.ResetFlags() + pfad := "./test/testdata/cli_testdata/auditDummy2.jba" + os.Args = []string{gopath, "-auditConfig=" + pfad} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.AuditConfig != pfad { + t.Errorf("Audit-Config-Pfad wurde nicht richtig gesetzt:\nIst: " + cs.AuditConfig + "\nSoll: " + pfad) + } +} + +func TestAuditConfigFlag2(t *testing.T) { + config_parser.ResetFlags() + pfad := "./test/testdata/cli_testdata/auditDummy2.jba" + os.Args = []string{gopath, "-a=" + pfad} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.AuditConfig != pfad { + t.Errorf("Audit-Config-Pfad wurde nicht richtig gesetzt:\nIst: " + cs.AuditConfig + "\nSoll: " + pfad) + } +} + +func TestConfigFlag(t *testing.T) { + os.Args = []string{gopath, "-config=./test/testdata/cli_testdata/basic_config.ini"} + cs, log := config_parser.LoadConfig() + pfad := filepath.Clean(gopath + "/test/testdata/cli_testdata/basic_config.ini") + + _, err := config_interpreter.InterpretConfig(&cs) + if err != nil { + t.Errorf("Fehler beim Interpretieren.") + } + + EvaluateLog(t, log, cs) + if cs.Config != pfad { + t.Errorf("Config-Pfad wurde nicht richtig gesetzt:\nIst: " + cs.Config + "\nSoll: " + pfad) + } +} + +func TestConfigFlag2(t *testing.T) { + os.Args = []string{gopath, "-c=./test/testdata/cli_testdata/basic_config.ini"} + cs, log := config_parser.LoadConfig() + pfad := filepath.Clean(gopath + "/test/testdata/cli_testdata/basic_config.ini") + + EvaluateLog(t, log, cs) + if cs.Config != pfad { + t.Errorf("Config-Pfad wurde nicht richtig gesetzt:\nIst: " + cs.Config + "\nSoll: " + pfad) + } +} + +func TestOutputFlag(t *testing.T) { + pfad := "./test/testdata/cli_testdata/" + os.Args = []string{gopath, "-outputPath=" + pfad} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.OutputPath != pfad { + t.Errorf("Output-Pfad wurde nicht richtig gesetzt:\nIst: " + cs.OutputPath + "\nSoll: " + pfad) + } +} + +func TestOutputFlag2(t *testing.T) { + pfad := "./test/testdata/cli_testdata/" + os.Args = []string{gopath, "-o=" + pfad} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.OutputPath != pfad { + t.Errorf("Output-Pfad wurde nicht richtig gesetzt:\nIst: " + cs.OutputPath + "\nSoll: " + pfad) + } +} + +func TestVerbosityLogFlag(t *testing.T) { + os.Args = []string{gopath, "-verbosityLog=2"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.VerbosityLog != 2 { + t.Errorf("Log-Verbosity wurde nicht richtig gesetzt:\nIst: " + strconv.Itoa(cs.VerbosityLog) + "\nSoll: " + "2") + } +} + +func TestVerbosityLogFlag2(t *testing.T) { + os.Args = []string{gopath, "-vl=2"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.VerbosityLog != 2 { + t.Errorf("Log-Verbosity wurde nicht richtig gesetzt:\nIst: " + strconv.Itoa(cs.VerbosityLog) + "\nSoll: " + "2") + } +} + +func TestVerbosityConsoleFlag(t *testing.T) { + os.Args = []string{gopath, "-verbosityConsole=3"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.VerbosityConsole != 3 { + t.Errorf("Log-Verbosity wurde nicht richtig gesetzt:\nIst: " + strconv.Itoa(cs.VerbosityConsole) + "\nSoll: " + "3") + } +} + +func TestVerbosityConsoleFlag2(t *testing.T) { + os.Args = []string{gopath, "-vc=3"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.VerbosityConsole != 3 { + t.Errorf("Log-Verbosity wurde nicht richtig gesetzt:\nIst: " + strconv.Itoa(cs.VerbosityConsole) + "\nSoll: " + "3") + } +} + +func TestSkipModuleCompatibilityFlag(t *testing.T) { + os.Args = []string{gopath, "-skipModuleCompatibilityCheck"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.SkipModuleCompatibilityCheck { + t.Errorf("SkipModuleCompatibilityCheck wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestSkipModuleCompatibilityFlag2(t *testing.T) { + os.Args = []string{gopath, "-skipModuleCompatibilityCheck=false"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.SkipModuleCompatibilityCheck { + t.Errorf("SkipModuleCompatibilityCheck wurde nicht richtig gesetzt:\nIst: true\nSoll: false") + } +} + +func TestKeepConsoleOpenFlag(t *testing.T) { + os.Args = []string{gopath, "-keepConsoleOpen"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.KeepConsoleOpen { + t.Errorf("KeepConsoleOpen wurde nicht richtig gesetzt:\nIst: true\nSoll: false") + } +} + +func TestForceOSFlag(t *testing.T) { + os.Args = []string{gopath, "-forceOS=\"testOS\""} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.ForceOS != "testOS" { + t.Errorf("forceOS wurde nicht richtig gesetzt:\nIst: " + cs.ForceOS + "\nSoll: testOS") + } +} + +func TestForceOSFlag2(t *testing.T) { + os.Args = []string{gopath, "-forceOS=testOS"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.ForceOS != "testOS" { + t.Errorf("forceOS wurde nicht richtig gesetzt:\nIst: " + cs.ForceOS + "\nSoll: testOS") + } +} + +func TestForceOSFlag3(t *testing.T) { + os.Args = []string{gopath, "-forceOS= testOS"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.ForceOS != "testOS" { + t.Errorf("forceOS wurde nicht richtig gesetzt:\nIst: " + cs.ForceOS + "\nSoll: testOS") + } +} + +func TestForceOSFlag4(t *testing.T) { + os.Args = []string{gopath, "--forceOS=testOS"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.ForceOS != "testOS" { + t.Errorf("forceOS wurde nicht richtig gesetzt:\nIst: " + cs.ForceOS + "\nSoll: testOS") + } +} + +func TestIgnoreMissingPrivilegesFlag(t *testing.T) { + os.Args = []string{gopath, "-ignoreMissingPrivileges"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.IgnoreMissingPrivileges { + t.Errorf("IgnoreMissingPrivileges wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestIgnoreMissingPrivilegesFlag2(t *testing.T) { + os.Args = []string{gopath, "-ignoreMissingPrivileges=true"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.IgnoreMissingPrivileges { + t.Errorf("IgnoreMissingPrivileges wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestAlwaysPrintProgressFlag(t *testing.T) { + os.Args = []string{gopath, "-alwaysPrintProgress"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.AlwaysPrintProgress { + t.Errorf("AlwaysPrintProgress wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestZipFlag(t *testing.T) { + os.Args = []string{gopath, "-zip"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.Zip { + t.Errorf("Zip wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestZipFlag2(t *testing.T) { + os.Args = []string{gopath, "-zip=true"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.Zip { + t.Errorf("Zip wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestZipOnlyFlag(t *testing.T) { + os.Args = []string{gopath, "-zipOnly"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.ZipOnly { + t.Errorf("ZipOnly wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestZipOnlyFlag2(t *testing.T) { + os.Args = []string{gopath, "-zipOnly=true"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.ZipOnly { + t.Errorf("ZipOnly wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestZipAndZipOnlyFlag(t *testing.T) { + os.Args = []string{gopath, "-zipOnly", "-zip"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if _, err := config_interpreter.InterpretConfig(&cs); err == nil { + t.Errorf("Zip und ZipOnly wurden gleichzeitig gesetzt, der Fehler wurde nicht erkannt.") + } +} + +func TestVersionFlag(t *testing.T) { + os.Args = []string{gopath, "-version"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.Version { + t.Errorf("Version wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestShowModulesFlag(t *testing.T) { + os.Args = []string{gopath, "-showModules"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.ShowModule != "all" { + t.Errorf("ShowModule wurde nicht richtig gesetzt:\nIst: " + cs.ShowModule + "\nSoll: all") + } +} + +func TestShowModulesFlag2(t *testing.T) { + os.Args = []string{gopath, "-showModuleInfo=test"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if cs.ShowModule != "test" { + t.Errorf("ShowModule wurde nicht richtig gesetzt:\nIst: " + cs.ShowModule + "\nSoll: test") + } +} + +func TestCheckConfigurationFlag(t *testing.T) { + os.Args = []string{gopath, "-checkConfiguration"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.CheckConfiguration { + t.Errorf("CheckConfiguration wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestCheckSyntaxFlag(t *testing.T) { + os.Args = []string{gopath, "-checkSyntax"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.CheckSyntax { + t.Errorf("CheckSyntax wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestCheckSyntaxFlag2(t *testing.T) { + os.Args = []string{gopath, "-syntax"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.CheckSyntax { + t.Errorf("CheckSyntax wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestSaveConfigurationFlag(t *testing.T) { + os.Args = []string{gopath, "-saveConfiguration"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.SaveConfiguration { + t.Errorf("Save wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestSaveConfigurationFlag2(t *testing.T) { + os.Args = []string{gopath, "-s"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.SaveConfiguration { + t.Errorf("Save wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} + +func TestCreateDefaultConfigFlag(t *testing.T) { + os.Args = []string{gopath, "-createDefault"} + cs, log := config_parser.LoadConfig() + + EvaluateLog(t, log, cs) + if !cs.CreateDefaultConfig { + t.Errorf("CreateDefaultConfig wurde nicht richtig gesetzt:\nIst: false\nSoll: true") + } +} diff --git a/test/config_test/cli_configini_commandline_test.go b/test/config_test/cli_configini_commandline_test.go new file mode 100644 index 0000000..630941f --- /dev/null +++ b/test/config_test/cli_configini_commandline_test.go @@ -0,0 +1,51 @@ +package config_test + +import ( + "fmt" + config_parser "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-parser" + "os" + "strconv" + "testing" +) + +func TestCmdOverwriteAuditConfig(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/overwrite_audconf.ini"`, `-a="test2"`} + cs, log := config_parser.LoadConfig() + EvaluateLog(t, log, cs) + + if cs.AuditConfig != "test2" { + t.Error("Parameter wurde nicht korrekt überschrieben: " + cs.AuditConfig) + } +} + +func TestCmdOverwriteOutput(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/overwrite_output.ini"`, `-o="test2"`} + cs, log := config_parser.LoadConfig() + EvaluateLog(t, log, cs) + + if cs.OutputPath != "test2" { + t.Error("Parameter wurde nicht korrekt überschrieben: " + cs.AuditConfig) + } +} + +func TestCmdOverwriteVerbosity(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/overwrite_verbosity.ini"`, `-verbosityConsole=4`, `-verbosityLog=4`} + cs, log := config_parser.LoadConfig() + fmt.Println(log) + EvaluateLog(t, log, cs) + + if cs.VerbosityLog != 4 || cs.VerbosityConsole != 4 { + t.Error("Parameter wurde nicht korrekt überschrieben: " + strconv.Itoa(cs.VerbosityLog) + " " + strconv.Itoa(cs.VerbosityConsole)) + } +} + +func TestCmdOverwriteBooleans(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/overwrite_booleans.ini"`, `-skipModuleCompatibilityCheck=false`, `-keepConsoleOpen=false`, "-ignoreMissingPrivileges=false", "-alwaysPrintProgress=false", "-zip=false", "-zipOnly=false"} + cs, log := config_parser.LoadConfig() + fmt.Println(log) + EvaluateLog(t, log, cs) + + if cs.SkipModuleCompatibilityCheck || cs.KeepConsoleOpen || cs.IgnoreMissingPrivileges || cs.AlwaysPrintProgress || cs.Zip || cs.ZipOnly { + t.Error("Einer der Booleans wurde nicht korrekt überschrieben: " + fmt.Sprint(cs.SkipModuleCompatibilityCheck) + fmt.Sprint(cs.KeepConsoleOpen) + fmt.Sprint(cs.IgnoreMissingPrivileges) + fmt.Sprint(cs.AlwaysPrintProgress) + fmt.Sprint(cs.Zip) + fmt.Sprint(cs.ZipOnly)) + } +} diff --git a/test/config_test/cli_configini_test.go b/test/config_test/cli_configini_test.go new file mode 100644 index 0000000..6cfdbb2 --- /dev/null +++ b/test/config_test/cli_configini_test.go @@ -0,0 +1,99 @@ +package config_test + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-interpreter" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-parser" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "os" + s "strings" + "testing" +) + +func TestCLIBasicConfig(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/basic_config.ini"`} + cs, log := config_parser.LoadConfig() + EvaluateLog(t, log, cs) + InterpretConfig(t, &cs) +} + +func TestCLIMixedOrder(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/mixed_order.ini"`} + cs, log := config_parser.LoadConfig() + EvaluateLog(t, log, cs) + InterpretConfig(t, &cs) +} + +func TestCLISpacesAndQuotation(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/spaces_and_quotation.ini"`} + cs, log := config_parser.LoadConfig() + EvaluateLog(t, log, cs) + InterpretConfig(t, &cs) +} + +func TestCLIMissingKey(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/missing_key.ini"`} + cs, log := config_parser.LoadConfig() + err := GetLogErr(log) + + if !s.HasPrefix(err, "Ungültiger Wert (fehlender Key)") { + t.Errorf("Fehlgeschlagen. Fehlender Key wurde nicht erkannt.") + } + InterpretConfig(t, &cs) +} + +func TestCLIMissingValues(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/missing_values.ini"`} + _, log := config_parser.LoadConfig() + err := GetLogErr(log) + if !s.HasPrefix(err, "Der Wert eines Keys darf nicht leer sein") { + t.Errorf("Fehlgeschlagen. Fehlender Wert wurde nicht erkannt.") + } +} + +func TestCLIMissingValuesExceptLoglevel(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/missing_values_except_loglevel.ini"`} + _, log := config_parser.LoadConfig() + err := GetLogErr(log) + if !s.HasPrefix(err, "Der Wert eines Keys darf nicht leer sein") { + t.Errorf("Fehlgeschlagen. Fehlender Wert wurde nicht erkannt.") + } +} + +func TestCLINegativeLoglevel(t *testing.T) { + os.Args = []string{gopath, `-config="./test/testdata/cli_testdata/negative_loglevel.ini"`} + cs, log := config_parser.LoadConfig() + EvaluateLog(t, log, cs) + + _, err := config_interpreter.InterpretConfig(&cs) + if err != nil { + t.Errorf("Unbekannter Fehler beim Interpretieren: " + err.Error()) + } + + err = logger.InitializeLogger(&cs, log) + if err == nil || err.Error() != "Bitte ein Loglevel zwischen 0 und 4 angeben." { + t.Errorf("Fehler wurde nicht erkannt") + } +} + +func TestCLIStaticPath(t *testing.T) { + os.Args = []string{gopath, `-config=` + gopath + `/test/testdata/cli_testdata/basic_config.ini`} + config, log := config_parser.LoadConfig() + EvaluateLog(t, log, config) +} + +func TestEmptyLines(t *testing.T) { + os.Args = []string{gopath, `-config=./test/testdata/cli_testdata/empty_lines.ini`} + cs, err := config_parser.LoadConfig() + strerr := GetLogErr(err) + if strerr != "" { + t.Errorf("Fehlgeschlagen: %v, %v", err, cs) + } + InterpretConfig(t, &cs) +} + +func TestCLIStaticConfig(t *testing.T) { + os.Args = []string{gopath, `-config=` + getStaticConf("./test/testdata/cli_testdata/static.ini")} + cs, log := config_parser.LoadConfig() + EvaluateLog(t, log, cs) + InterpretConfig(t, &cs) +} diff --git a/test/config_test/cli_test.go b/test/config_test/cli_test.go new file mode 100644 index 0000000..a797ba0 --- /dev/null +++ b/test/config_test/cli_test.go @@ -0,0 +1,168 @@ +package config_test + +import ( + "fmt" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-interpreter" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/config/config-parser" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "io/ioutil" + "log" + "os" + "path/filepath" + s "strings" + "testing" +) + +var ( + gopath = os.Getenv("gopath") + `\src\github.com\Jungbusch-Softwareschmiede\jungbusch-auditorium\` +) + +func InterpretConfig(t *testing.T, cs *ConfigStruct) { + logger.InitializeLogger(cs, []LogMsg{}) + con, err := config_interpreter.InterpretConfig(cs) + if err != nil { + t.Errorf("Fehlgeschlagen: Error aufgetreten: " + err.Error()) + } + if !con { + t.Errorf("Continue ist false, obwohl keine One-And-Done Parameter gesetzt wurde.") + } + config_parser.ResetFlags() +} + +func EvaluateLog(t *testing.T, log []LogMsg, cs ConfigStruct) { + err := GetLogErr(log) + if err != "" { + t.Errorf("Fehlgeschlagen: %v, %v", err, cs) + } + config_parser.ResetFlags() +} + +func printLog(log []LogMsg) { + for _, msg := range log { + fmt.Println(msg.Message) + } +} + +func logCleaner(log []LogMsg, iscontinue bool) (result string) { + for _, msg := range log { + if (!iscontinue && msg.AlwaysPrint) || iscontinue { + result = msg.Message + "\n" + } + } + return +} + +// Gibt den ersten Error aus dem Log zurück, wenn vorhanden +func GetLogErr(msg []LogMsg) string { + for n := range msg { + if msg[n].Level == 1 { + return msg[n].Message + } + } + return "" +} + +func deletLocalConfigs(dirPath string) { + // Im ganzen Verzeichniss nach '_local' Datein suchen und diese löschen + dirPath, err := util.GetAbsolutePath(dirPath) + if err != nil { + fmt.Println(err) + } + files, err := ioutil.ReadDir(dirPath) + if err != nil { + log.Fatal(err) + } + + for _, file := range files { + if s.Contains(file.Name(), "_local") { + if err := os.Remove(dirPath + `\` + file.Name()); err != nil { + fmt.Println(err) + } + } + } +} + +func getStaticConf(filePath string) string { + // Aktuelle config einlesen + fileSlice, err := util.ReadFile(filePath) + if err != nil { + fmt.Println(err) + } + + // Neues leeres FileSlice + var newFileSlice []string + for _, line := range fileSlice { + // Zeilen mit '%' + if s.Contains(line, "%") { + // Pfad zwischen '%' extrahieren und in Absoluten umwandeln + path := util.GetStringInBetween(line, "%") + absPath, err := util.GetAbsolutePath(path) + if err != nil { + fmt.Println(err) + } + // Pfad der config mit absolutem ersetzen + newLine := s.ReplaceAll(line, "%"+path+"%", absPath) + newFileSlice = append(newFileSlice, newLine) + } else { + newFileSlice = append(newFileSlice, line) + } + } + + // Neue Config mit _local im Dateinamen anlegen + newFileName := filePath[:s.LastIndex(filePath, ".")] + "_local" + filePath[s.LastIndex(filePath, "."):] + err = util.CreateFile(newFileSlice, newFileName) + if err != nil { + fmt.Println(err) + } + return newFileName +} + +func TestShowFlag(t *testing.T) { + os.Args = []string{gopath, `-showModules`} + config, log := config_parser.LoadConfig() + fmt.Println(log, config) +} + +func TestCLIDynamic(t *testing.T) { + os.Args = []string{gopath, `-config=./test/testdata/cli_testdata/basic_config.ini`} + config, log := config_parser.LoadConfig() + EvaluateLog(t, log, config) +} + +func TestCLIParameter(t *testing.T) { + match := ConfigStruct{ + AuditConfig: filepath.Clean(gopath + "/test/testdata/cli_testdata/auditDummy2.jba"), + OutputPath: filepath.Clean(gopath + "/test/testdata/cli_testdata/"), + Config: filepath.Clean(gopath + "/test/testdata/cli_testdata/basic_config.ini"), + VerbosityLog: 0, + VerbosityConsole: 0, + SkipModuleCompatibilityCheck: false, + } + os.Args = []string{gopath, "-config=./test/testdata/cli_testdata/basic_config.ini", "-auditConfig=./test/testdata/cli_testdata/auditDummy2.jba", "-outputPath=./test/testdata/cli_testdata/", "1", "1"} + + cs, log := config_parser.LoadConfig() + EvaluateLog(t, log, cs) + _, err := config_interpreter.InterpretConfig(&cs) + + if err != nil { + t.Errorf(err.Error()) + } + + err = logger.InitializeOutput(&cs) + if err != nil { + t.Errorf(err.Error()) + } + + err = logger.InitializeLogger(&cs, log) + if err != nil { + t.Errorf(err.Error()) + } + + cs.OutputPath = filepath.Dir(cs.OutputPath) + + if cs != match { + t.Errorf("Ungleiche Config!\nSoll: %v\nIst: %v", match, cs) + } +} diff --git a/test/modules_test/script_test.go b/test/modules_test/script_test.go new file mode 100644 index 0000000..3ab5355 --- /dev/null +++ b/test/modules_test/script_test.go @@ -0,0 +1,69 @@ +package modules_test + +import ( + "fmt" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/modules" + "os" + "strings" + "testing" +) + +func TestScript(t *testing.T) { + path := strings.ReplaceAll(os.Getenv("gopath"), "\\", "\\\\") + + mh := modules.MethodHandler{} + params := models.ParameterMap{ + "script": `function runModule() { +params.file = "` + path + `\\src\\github.com\\Jungbusch-Softwareschmiede\\jungbusch-auditorium\\test\\testdata\\filme.txt"; +params.grep = "Rosso"; + +res = FileContent(params); +if(test == 'TEST_WERT') { +test = 'GEÄNDERT'; +return res; +} +if(res.result === "Porco Rosso\n"){ + params.command = "hostname" + params.grep = "" + res = ExecuteCommand(params) +} +return res; +} +runModule(); +`, + } + variables := models.VariableMap{"%test%": models.Variable{ + Name: "%test%", + Value: "TEST_WERT", + IsEnv: false, + }} + result := mh.Script(params, &variables) + + if result.Err != nil { + t.Errorf("Fehlgeschlagen: %v", result.Err) + } + + fmt.Println(result.Result) + fmt.Println(variables) +} + +func TestCustomResult(t *testing.T) { + mh := modules.MethodHandler{} + params := models.ParameterMap{ + "script": `function runModule() { +result = newResult('Hallo', 'Hallo dies ist ein Test', null); + +return result; +} +runModule();`, + } + + result := mh.Script(params, &models.VariableMap{}) + + if result.Result != "Hallo" { + t.Errorf("Result nicht richtig: " + result.Result) + } else if result.ResultRaw != "Hallo dies ist ein Test" { + t.Errorf("ResultRaw nicht richtig: " + result.ResultRaw) + } +} diff --git a/test/outputgenerator_test/output_generator_test.go b/test/outputgenerator_test/output_generator_test.go new file mode 100644 index 0000000..7632a63 --- /dev/null +++ b/test/outputgenerator_test/output_generator_test.go @@ -0,0 +1,101 @@ +package outputgenerator_test + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/auditconfig/interpreter" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/modulecontroller" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/auditorium/outputgenerator" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/logger" + "os" + s "strings" + "testing" + "time" +) + +func initVarMap() VariableMap { + varSlice := make(VariableMap) + varSlice["%result%"] = Variable{Name: "%result%", IsEnv: true} + varSlice["%passed%"] = Variable{Name: "%passed%", IsEnv: true} + varSlice["%unsuccessful%"] = Variable{Name: "%unsuccessful%", IsEnv: true} + varSlice["%os%"] = Variable{Name: "%os%", Value: static.OperatingSystem, IsEnv: true} + varSlice["%currentmodule%"] = Variable{Name: "%currentmodule%", IsEnv: true} + return varSlice +} + +func TestGenerateReport(t *testing.T) { + gopath := os.Getenv("gopath") + `\src\github.com\Jungbusch-Softwareschmiede\jungbusch-auditorium\` + + if err := logger.InitializeLogger(&ConfigStruct{}, []LogMsg{}); err != nil { + t.Errorf(err.Error()) + } + e := AuditModule{ + ModuleName: "ExecuteCommand", + StepID: "Hostname", + Passed: "true", + Variables: initVarMap(), + ModuleParameters: ParameterMap{"command": "hostname"}, + } + u := AuditModule{ + ModuleName: "FileContent", + StepID: "Film-Inhalt3", + Passed: "%result% === 'Porco Rosso'", + Variables: initVarMap(), + ModuleParameters: ParameterMap{"file": "test"}, + } + n := AuditModule{ + ModuleName: "FileContent", + StepID: "Film-Inhalt2", + Passed: "false", + Variables: initVarMap(), + ModuleParameters: ParameterMap{"file": gopath + "test\\testdata\\filme.txt", "grep": "Porco"}, + NestedModules: []AuditModule{u}, + } + p := AuditModule{ + ModuleName: "FileContent", + StepID: "Film-Inhalt1", + Passed: "%result% === 'Porco Rosso'", + Variables: initVarMap(), + ModuleParameters: ParameterMap{"file": gopath + "test\\testdata\\filme.txt", "grep": "Porco"}, + NestedModules: []AuditModule{n}, + } + a := AuditModule{ + ModuleName: "Script", + StepID: "TestScript", + Passed: "true", + Variables: initVarMap(), + ModuleParameters: ParameterMap{ + "script": `function runModule() { +params.file = "` + s.ReplaceAll(gopath, "\\", "\\\\") + `test\\testdata\\filme.txt"; +params.grep = "Rosso"; + +r = FileContent(params); + if (r.result == 'Porco Rosso') { + params.command = "ipconfig"; + params.grep = "" + r = ExecuteCommand(params); +} +return r; +} +runModule(); +`, + }, + } + + static.OperatingSystem, _ = modulecontroller.GetOS() + ms := []AuditModule{p, e, a} + + r, err := interpreter.InterpretAudit(ms, 5, true) + + if err != nil { + t.Error(err) + } else { + + if err = outputgenerator.GenerateOutput(r, gopath+"ProgrammOutput/testoutput", 5, false, time.Now(), time.Since(time.Now())); err != nil { + t.Errorf(err.Error()) + } + } + + _ = os.RemoveAll(gopath + "ProgrammOutput/testoutput") + t.Log("Success") +} diff --git a/test/testdata/cli_testdata/Test1/audit.jba b/test/testdata/cli_testdata/Test1/audit.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test1/audit_in_folder.jba b/test/testdata/cli_testdata/Test1/audit_in_folder.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test1/folder_with_audit/cli_audit.jba b/test/testdata/cli_testdata/Test1/folder_with_audit/cli_audit.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test10/config.ini b/test/testdata/cli_testdata/Test10/config.ini new file mode 100644 index 0000000..1c390b1 --- /dev/null +++ b/test/testdata/cli_testdata/Test10/config.ini @@ -0,0 +1,2 @@ +[ENVIRONMENT] +AuditConfig=.//audit.jba \ No newline at end of file diff --git a/test/testdata/cli_testdata/Test11/config.ini b/test/testdata/cli_testdata/Test11/config.ini new file mode 100644 index 0000000..66a10e8 --- /dev/null +++ b/test/testdata/cli_testdata/Test11/config.ini @@ -0,0 +1,2 @@ +[ENVIRONMENT] +AuditConfig=./folder_with_audit \ No newline at end of file diff --git a/test/testdata/cli_testdata/Test12/audit.txt b/test/testdata/cli_testdata/Test12/audit.txt new file mode 100644 index 0000000..39fb21e --- /dev/null +++ b/test/testdata/cli_testdata/Test12/audit.txt @@ -0,0 +1 @@ +hallu \ No newline at end of file diff --git a/test/testdata/cli_testdata/Test12/config.ini b/test/testdata/cli_testdata/Test12/config.ini new file mode 100644 index 0000000..90ce7af --- /dev/null +++ b/test/testdata/cli_testdata/Test12/config.ini @@ -0,0 +1,2 @@ +[ENVIRONMENT] +AuditConfig=./audit.txt \ No newline at end of file diff --git a/test/testdata/cli_testdata/Test2/audit.jba b/test/testdata/cli_testdata/Test2/audit.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test2/audit_in_folder.jba b/test/testdata/cli_testdata/Test2/audit_in_folder.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test2/config.ini b/test/testdata/cli_testdata/Test2/config.ini new file mode 100644 index 0000000..00b27f4 --- /dev/null +++ b/test/testdata/cli_testdata/Test2/config.ini @@ -0,0 +1,2 @@ +[ENVIRONMENT] +AuditConfig=./folder_with_audit/config_audit.jba \ No newline at end of file diff --git a/test/testdata/cli_testdata/Test2/folder_with_audit/config_audit.jba b/test/testdata/cli_testdata/Test2/folder_with_audit/config_audit.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test3/testauditxy.jba b/test/testdata/cli_testdata/Test3/testauditxy.jba new file mode 100644 index 0000000..2e80699 --- /dev/null +++ b/test/testdata/cli_testdata/Test3/testauditxy.jba @@ -0,0 +1,5 @@ +{ + module: "ExecuteCommand" + command:"hostname" + passed: if("%result% != """) +}, \ No newline at end of file diff --git a/test/testdata/cli_testdata/Test4/audit.jba b/test/testdata/cli_testdata/Test4/audit.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test4/audit_in_folder.jba b/test/testdata/cli_testdata/Test4/audit_in_folder.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test5/audit_in_folder1.jba b/test/testdata/cli_testdata/Test5/audit_in_folder1.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test5/audit_in_folder2.jba b/test/testdata/cli_testdata/Test5/audit_in_folder2.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test7/audit1.jba b/test/testdata/cli_testdata/Test7/audit1.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test7/audit2.jba b/test/testdata/cli_testdata/Test7/audit2.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test7/config.ini b/test/testdata/cli_testdata/Test7/config.ini new file mode 100644 index 0000000..eaf8393 --- /dev/null +++ b/test/testdata/cli_testdata/Test7/config.ini @@ -0,0 +1,2 @@ +[ENVIRONMENT] +AuditConfig=./folder_with_audit/audit_in_folder.jba \ No newline at end of file diff --git a/test/testdata/cli_testdata/Test7/folder_with_audit/audit_in_folder.jba b/test/testdata/cli_testdata/Test7/folder_with_audit/audit_in_folder.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test8/testaudit.jba b/test/testdata/cli_testdata/Test8/testaudit.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/Test9/config.ini b/test/testdata/cli_testdata/Test9/config.ini new file mode 100644 index 0000000..dbff776 --- /dev/null +++ b/test/testdata/cli_testdata/Test9/config.ini @@ -0,0 +1,2 @@ +[ENVIRONMENT] +AuditConfig=./audit.jba \ No newline at end of file diff --git a/test/testdata/cli_testdata/auditDummy.jba b/test/testdata/cli_testdata/auditDummy.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/auditDummy2.jba b/test/testdata/cli_testdata/auditDummy2.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/cli_testdata/basic_config.ini b/test/testdata/cli_testdata/basic_config.ini new file mode 100644 index 0000000..fd2d99e --- /dev/null +++ b/test/testdata/cli_testdata/basic_config.ini @@ -0,0 +1,5 @@ +[ENVIRONMENT] +AuditConfig=./test/testdata/cli_testdata/auditDummy.jba +OutputPath=./test/testdata/cli_testdata/ +VerbosityLog=0 +VerbosityConsole=0 \ No newline at end of file diff --git a/test/testdata/cli_testdata/empty_lines.ini b/test/testdata/cli_testdata/empty_lines.ini new file mode 100644 index 0000000..fd2d99e --- /dev/null +++ b/test/testdata/cli_testdata/empty_lines.ini @@ -0,0 +1,5 @@ +[ENVIRONMENT] +AuditConfig=./test/testdata/cli_testdata/auditDummy.jba +OutputPath=./test/testdata/cli_testdata/ +VerbosityLog=0 +VerbosityConsole=0 \ No newline at end of file diff --git a/test/testdata/cli_testdata/missing_key.ini b/test/testdata/cli_testdata/missing_key.ini new file mode 100644 index 0000000..4e5d043 --- /dev/null +++ b/test/testdata/cli_testdata/missing_key.ini @@ -0,0 +1,5 @@ +[ENVIRONMENT] +AuditConfig=/test/testdata/cli_testdata/auditDummy.jba +/testdata/cli_testdata/ +VerbosityLog=0 +VerbosityConsole=0 \ No newline at end of file diff --git a/test/testdata/cli_testdata/missing_values.ini b/test/testdata/cli_testdata/missing_values.ini new file mode 100644 index 0000000..e8b89a3 --- /dev/null +++ b/test/testdata/cli_testdata/missing_values.ini @@ -0,0 +1,5 @@ +[ENVIRONMENT] +AuditConfig= +OutputPath= +VerbosityLog= +VerbosityConsole= diff --git a/test/testdata/cli_testdata/missing_values_except_loglevel.ini b/test/testdata/cli_testdata/missing_values_except_loglevel.ini new file mode 100644 index 0000000..f9eb5df --- /dev/null +++ b/test/testdata/cli_testdata/missing_values_except_loglevel.ini @@ -0,0 +1,5 @@ +[ENVIRONMENT] +AuditConfig= +OutputPath= +VerbosityLog=0 +VerbosityConsole=0 diff --git a/test/testdata/cli_testdata/mixed_order.ini b/test/testdata/cli_testdata/mixed_order.ini new file mode 100644 index 0000000..480cb9b --- /dev/null +++ b/test/testdata/cli_testdata/mixed_order.ini @@ -0,0 +1,5 @@ +[ENVIRONMENT] +VerbosityConsole=0 +AuditConfig=./test/testdata/cli_testdata/auditDummy.jba +VerbosityLog=0 +OutputPath=./test/testdata/cli_testdata/ diff --git a/test/testdata/cli_testdata/negative_loglevel.ini b/test/testdata/cli_testdata/negative_loglevel.ini new file mode 100644 index 0000000..ff998a3 --- /dev/null +++ b/test/testdata/cli_testdata/negative_loglevel.ini @@ -0,0 +1,5 @@ +[ENVIRONMENT] +AuditConfig=/test/testdata/cli_testdata/auditDummy.jba +OutputPath=/test/testdata/cli_testdata/ +VerbosityLog =-1 +VerbosityConsole=-2 diff --git a/test/testdata/cli_testdata/overwrite_audconf.ini b/test/testdata/cli_testdata/overwrite_audconf.ini new file mode 100644 index 0000000..1d24f81 --- /dev/null +++ b/test/testdata/cli_testdata/overwrite_audconf.ini @@ -0,0 +1,2 @@ +[ENVIRONMENT] +AuditConfig=test1 \ No newline at end of file diff --git a/test/testdata/cli_testdata/overwrite_booleans.ini b/test/testdata/cli_testdata/overwrite_booleans.ini new file mode 100644 index 0000000..a2cb996 --- /dev/null +++ b/test/testdata/cli_testdata/overwrite_booleans.ini @@ -0,0 +1,7 @@ +[ENVIRONMENT] +SkipModuleCompatibilityCheck=true +KeepConsoleOpen=true +IgnoreMissingPrivileges=true +AlwaysPrintProgress=true +Zip=true +ZipOnly=true \ No newline at end of file diff --git a/test/testdata/cli_testdata/overwrite_output.ini b/test/testdata/cli_testdata/overwrite_output.ini new file mode 100644 index 0000000..a497389 --- /dev/null +++ b/test/testdata/cli_testdata/overwrite_output.ini @@ -0,0 +1,2 @@ +[ENVIRONMENT] +OutputPath=test1 \ No newline at end of file diff --git a/test/testdata/cli_testdata/overwrite_verbosity.ini b/test/testdata/cli_testdata/overwrite_verbosity.ini new file mode 100644 index 0000000..6d1bf07 --- /dev/null +++ b/test/testdata/cli_testdata/overwrite_verbosity.ini @@ -0,0 +1,3 @@ +[ENVIRONMENT] +verbosityConsole=1 +verbosityLog=1 \ No newline at end of file diff --git a/test/testdata/cli_testdata/spaces_and_quotation.ini b/test/testdata/cli_testdata/spaces_and_quotation.ini new file mode 100644 index 0000000..5bc7c81 --- /dev/null +++ b/test/testdata/cli_testdata/spaces_and_quotation.ini @@ -0,0 +1,5 @@ +[ENVIRONMENT] +AuditConfig="/test/testdata/cli_testdata/auditDummy.jba" +OutputPath= /test/testdata/cli_testdata/ +VerbosityLog = "0" +VerbosityConsole= 0" \ No newline at end of file diff --git a/test/testdata/cli_testdata/static.ini b/test/testdata/cli_testdata/static.ini new file mode 100644 index 0000000..0e813ed --- /dev/null +++ b/test/testdata/cli_testdata/static.ini @@ -0,0 +1,4 @@ +AuditConfig=%./test/testdata/cli_testdata/auditDummy.jba% +OutputPath=%./test/testdata/cli_testdata/% +VerbosityLog=0 +VerbosityConsole=0 \ No newline at end of file diff --git a/test/testdata/cli_testdata/static_local.ini b/test/testdata/cli_testdata/static_local.ini new file mode 100644 index 0000000..6e9da8a --- /dev/null +++ b/test/testdata/cli_testdata/static_local.ini @@ -0,0 +1,4 @@ +AuditConfig=C:\Users\byte\go\src\github.com\Jungbusch-Softwareschmiede\jungbusch-auditorium\test\testdata\cli_testdata\auditDummy.jba +OutputPath=C:\Users\byte\go\src\github.com\Jungbusch-Softwareschmiede\jungbusch-auditorium\test\testdata\cli_testdata +VerbosityLog=0 +VerbosityConsole=0 diff --git a/test/testdata/filme.txt b/test/testdata/filme.txt new file mode 100644 index 0000000..b59d1ed --- /dev/null +++ b/test/testdata/filme.txt @@ -0,0 +1,12 @@ +Porco Rosso +TED 2 +Annihilation +Nightcrawler +Snowpiercer +Departed +Der Junge Und Das Biest +Goodfellas +Mortal Kombat +Mad Max Furry Road uwu +Res8ident Evile +Naked Gun \ No newline at end of file diff --git a/test/testdata/parser_testdata/additional_alias.jba b/test/testdata/parser_testdata/additional_alias.jba new file mode 100644 index 0000000..994c65f --- /dev/null +++ b/test/testdata/parser_testdata/additional_alias.jba @@ -0,0 +1,8 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + grep: "test" + datei: "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/additional_comma.jba b/test/testdata/parser_testdata/additional_comma.jba new file mode 100644 index 0000000..8e06ac1 --- /dev/null +++ b/test/testdata/parser_testdata/additional_comma.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +},, \ No newline at end of file diff --git a/test/testdata/parser_testdata/backticks.jba b/test/testdata/parser_testdata/backticks.jba new file mode 100644 index 0000000..05f7e68 --- /dev/null +++ b/test/testdata/parser_testdata/backticks.jba @@ -0,0 +1,7 @@ +{ + condition: if(`%test% == "true"`) + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if (`%result% == "test"`) +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/basic_module.jba b/test/testdata/parser_testdata/basic_module.jba new file mode 100644 index 0000000..63dd5d0 --- /dev/null +++ b/test/testdata/parser_testdata/basic_module.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/basic_module_nested.jba b/test/testdata/parser_testdata/basic_module_nested.jba new file mode 100644 index 0000000..0bc1fb3 --- /dev/null +++ b/test/testdata/parser_testdata/basic_module_nested.jba @@ -0,0 +1,12 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/brace_in_param.jba b/test/testdata/parser_testdata/brace_in_param.jba new file mode 100644 index 0000000..6c61d92 --- /dev/null +++ b/test/testdata/parser_testdata/brace_in_param.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + grep:"t{est" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/brace_in_value.jba b/test/testdata/parser_testdata/brace_in_value.jba new file mode 100644 index 0000000..67ae12b --- /dev/null +++ b/test/testdata/parser_testdata/brace_in_value.jba @@ -0,0 +1,6 @@ +{ + module:"FileContent" + stepid:"1" + file:"test" + %test% = "foo{bar" +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/case_sensitivity1.jba b/test/testdata/parser_testdata/case_sensitivity1.jba new file mode 100644 index 0000000..239e717 --- /dev/null +++ b/test/testdata/parser_testdata/case_sensitivity1.jba @@ -0,0 +1,14 @@ +{ + %Test% = %passed% + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + condition:if("%test%") + id:"testmodul2" + module:"ExecuteCommand" + command:"test" + passed: if("test") + }, +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/case_sensitivity2.jba b/test/testdata/parser_testdata/case_sensitivity2.jba new file mode 100644 index 0000000..684e573 --- /dev/null +++ b/test/testdata/parser_testdata/case_sensitivity2.jba @@ -0,0 +1,14 @@ +{ + %test% = %passed% + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + condition:if("%Test%") + id:"testmodul2" + module:"ExecuteCommand" + command:"test" + passed: if("test") + }, +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/case_sensitivity_script1.jba b/test/testdata/parser_testdata/case_sensitivity_script1.jba new file mode 100644 index 0000000..18dac33 --- /dev/null +++ b/test/testdata/parser_testdata/case_sensitivity_script1.jba @@ -0,0 +1,9 @@ +{ + %Test% = "%/test/testfile%" + id:"testmodule" + module:"Script" + script:`function runModule(){ + return test; + } + runModule();` +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/case_sensitivity_script2.jba b/test/testdata/parser_testdata/case_sensitivity_script2.jba new file mode 100644 index 0000000..0bec017 --- /dev/null +++ b/test/testdata/parser_testdata/case_sensitivity_script2.jba @@ -0,0 +1,9 @@ +{ + %test% = "%/test/testfile%" + id:"testmodule" + module:"Script" + script:`function runModule(){ + return Test; + } + runModule();` +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/colon_in_value.jba b/test/testdata/parser_testdata/colon_in_value.jba new file mode 100644 index 0000000..8bdf47f --- /dev/null +++ b/test/testdata/parser_testdata/colon_in_value.jba @@ -0,0 +1,6 @@ +{ + module:"FileContent" + stepid:"1" + file:"test" + %test% = "foo:bar" +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/colon_only.jba b/test/testdata/parser_testdata/colon_only.jba new file mode 100644 index 0000000..e14fb60 --- /dev/null +++ b/test/testdata/parser_testdata/colon_only.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + : + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/comment_after_brace.jba b/test/testdata/parser_testdata/comment_after_brace.jba new file mode 100644 index 0000000..9a525ad --- /dev/null +++ b/test/testdata/parser_testdata/comment_after_brace.jba @@ -0,0 +1,18 @@ +{ // test + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { // test + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, // test + { // test + id:"testmodule3" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, // test +}, // test \ No newline at end of file diff --git a/test/testdata/parser_testdata/comment_after_param.jba b/test/testdata/parser_testdata/comment_after_param.jba new file mode 100644 index 0000000..b76befa --- /dev/null +++ b/test/testdata/parser_testdata/comment_after_param.jba @@ -0,0 +1,18 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" // test + passed: if ("test") +}, +{ + id:"testmodule2" + module:"Script" + script:`test` // test +}, +{ + id:"testmodule3" + module:"Script" + script:`test; // test + test; // test + ` +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/comment_after_param_with_quotation_mark.jba b/test/testdata/parser_testdata/comment_after_param_with_quotation_mark.jba new file mode 100644 index 0000000..54fac6f --- /dev/null +++ b/test/testdata/parser_testdata/comment_after_param_with_quotation_mark.jba @@ -0,0 +1,18 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" // foo"bar + passed: if ("test") +}, +{ + id:"testmodule2" + module:"Script" + script:`test` // foo`bar +}, +{ + id:"testmodule3" + module:"Script" + script:`test; // foobar + test; // foobar + ` +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/comment_after_value.jba b/test/testdata/parser_testdata/comment_after_value.jba new file mode 100644 index 0000000..35f0f23 --- /dev/null +++ b/test/testdata/parser_testdata/comment_after_value.jba @@ -0,0 +1,7 @@ +{ + %test% = "test" // test + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/comment_after_value_with_quotation_mark.jba b/test/testdata/parser_testdata/comment_after_value_with_quotation_mark.jba new file mode 100644 index 0000000..f23bbcd --- /dev/null +++ b/test/testdata/parser_testdata/comment_after_value_with_quotation_mark.jba @@ -0,0 +1,7 @@ +{ + %test% = "test" // foo"bar + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, diff --git a/test/testdata/parser_testdata/description.jba b/test/testdata/parser_testdata/description.jba new file mode 100644 index 0000000..e973941 --- /dev/null +++ b/test/testdata/parser_testdata/description.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + description:"das ist eine description" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/description_invalid.jba b/test/testdata/parser_testdata/description_invalid.jba new file mode 100644 index 0000000..e128472 --- /dev/null +++ b/test/testdata/parser_testdata/description_invalid.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + description:10 + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/empty_file.jba b/test/testdata/parser_testdata/empty_file.jba new file mode 100644 index 0000000..e69de29 diff --git a/test/testdata/parser_testdata/empty_lines.jba b/test/testdata/parser_testdata/empty_lines.jba new file mode 100644 index 0000000..5b77586 --- /dev/null +++ b/test/testdata/parser_testdata/empty_lines.jba @@ -0,0 +1,12 @@ + + +{ + id:"testmodule" + module:"ExecuteCommand" + + command:"test" + passed: if ("test") +}, + + + diff --git a/test/testdata/parser_testdata/empty_module.jba b/test/testdata/parser_testdata/empty_module.jba new file mode 100644 index 0000000..f87372f --- /dev/null +++ b/test/testdata/parser_testdata/empty_module.jba @@ -0,0 +1 @@ +{}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/empty_param_with_comment.jba b/test/testdata/parser_testdata/empty_param_with_comment.jba new file mode 100644 index 0000000..5d615f7 --- /dev/null +++ b/test/testdata/parser_testdata/empty_param_with_comment.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" // test + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/equal_sign_in_param.jba b/test/testdata/parser_testdata/equal_sign_in_param.jba new file mode 100644 index 0000000..a017c97 --- /dev/null +++ b/test/testdata/parser_testdata/equal_sign_in_param.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"te=st" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/equal_sign_only.jba b/test/testdata/parser_testdata/equal_sign_only.jba new file mode 100644 index 0000000..2d181b4 --- /dev/null +++ b/test/testdata/parser_testdata/equal_sign_only.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + = + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/global.jba b/test/testdata/parser_testdata/global.jba new file mode 100644 index 0000000..6a933e4 --- /dev/null +++ b/test/testdata/parser_testdata/global.jba @@ -0,0 +1,7 @@ +{ + %g_test% = "test" + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/global_overwrite.jba b/test/testdata/parser_testdata/global_overwrite.jba new file mode 100644 index 0000000..dec487c --- /dev/null +++ b/test/testdata/parser_testdata/global_overwrite.jba @@ -0,0 +1,20 @@ +{ + %g_test% = "test1" + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %g_test% = "test2" + id:"testmodule3" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/global_wrong_order.jba b/test/testdata/parser_testdata/global_wrong_order.jba new file mode 100644 index 0000000..dec487c --- /dev/null +++ b/test/testdata/parser_testdata/global_wrong_order.jba @@ -0,0 +1,20 @@ +{ + %g_test% = "test1" + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %g_test% = "test2" + id:"testmodule3" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/if_alias.jba b/test/testdata/parser_testdata/if_alias.jba new file mode 100644 index 0000000..8a1fa6f --- /dev/null +++ b/test/testdata/parser_testdata/if_alias.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: wenn ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/invalid_parameter_value.jba b/test/testdata/parser_testdata/invalid_parameter_value.jba new file mode 100644 index 0000000..8352386 --- /dev/null +++ b/test/testdata/parser_testdata/invalid_parameter_value.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command: test "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/invalid_variable_declaration1.jba b/test/testdata/parser_testdata/invalid_variable_declaration1.jba new file mode 100644 index 0000000..5842340 --- /dev/null +++ b/test/testdata/parser_testdata/invalid_variable_declaration1.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% = test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/invalid_variable_declaration2.jba b/test/testdata/parser_testdata/invalid_variable_declaration2.jba new file mode 100644 index 0000000..a3c1a4d --- /dev/null +++ b/test/testdata/parser_testdata/invalid_variable_declaration2.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% = "test + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/invalid_variable_declaration3.jba b/test/testdata/parser_testdata/invalid_variable_declaration3.jba new file mode 100644 index 0000000..a5882d2 --- /dev/null +++ b/test/testdata/parser_testdata/invalid_variable_declaration3.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% = test + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/keyword_sequence.jba b/test/testdata/parser_testdata/keyword_sequence.jba new file mode 100644 index 0000000..cbfc738 --- /dev/null +++ b/test/testdata/parser_testdata/keyword_sequence.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + passed: if ("test") + command:"test" + module:"ExecuteCommand" +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/leading_whitespace.jba b/test/testdata/parser_testdata/leading_whitespace.jba new file mode 100644 index 0000000..6c13274 --- /dev/null +++ b/test/testdata/parser_testdata/leading_whitespace.jba @@ -0,0 +1,6 @@ + { + id:"testmodule" + module: "ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_beginning_percent_sign.jba b/test/testdata/parser_testdata/missing_beginning_percent_sign.jba new file mode 100644 index 0000000..7b36815 --- /dev/null +++ b/test/testdata/parser_testdata/missing_beginning_percent_sign.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + test% = "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_brace.jba b/test/testdata/parser_testdata/missing_brace.jba new file mode 100644 index 0000000..195b3b9 --- /dev/null +++ b/test/testdata/parser_testdata/missing_brace.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_closing_quotation_mark.jba b/test/testdata/parser_testdata/missing_closing_quotation_mark.jba new file mode 100644 index 0000000..7707ddc --- /dev/null +++ b/test/testdata/parser_testdata/missing_closing_quotation_mark.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module: "ExecuteCommand + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_colon.jba b/test/testdata/parser_testdata/missing_colon.jba new file mode 100644 index 0000000..30acbf7 --- /dev/null +++ b/test/testdata/parser_testdata/missing_colon.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module "ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_comma.jba b/test/testdata/parser_testdata/missing_comma.jba new file mode 100644 index 0000000..4ec41e1 --- /dev/null +++ b/test/testdata/parser_testdata/missing_comma.jba @@ -0,0 +1,12 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +} +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +} \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_condition.jba b/test/testdata/parser_testdata/missing_condition.jba new file mode 100644 index 0000000..9048eb0 --- /dev/null +++ b/test/testdata/parser_testdata/missing_condition.jba @@ -0,0 +1,5 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_ending_percent_sign.jba b/test/testdata/parser_testdata/missing_ending_percent_sign.jba new file mode 100644 index 0000000..7c57692 --- /dev/null +++ b/test/testdata/parser_testdata/missing_ending_percent_sign.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test = "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_equal_sign.jba b/test/testdata/parser_testdata/missing_equal_sign.jba new file mode 100644 index 0000000..e3b5838 --- /dev/null +++ b/test/testdata/parser_testdata/missing_equal_sign.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_module_name.jba b/test/testdata/parser_testdata/missing_module_name.jba new file mode 100644 index 0000000..471bd38 --- /dev/null +++ b/test/testdata/parser_testdata/missing_module_name.jba @@ -0,0 +1,5 @@ +{ + id:"testmodule" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_opening_quotation_mark.jba b/test/testdata/parser_testdata/missing_opening_quotation_mark.jba new file mode 100644 index 0000000..7bdffd6 --- /dev/null +++ b/test/testdata/parser_testdata/missing_opening_quotation_mark.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module: ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_parameter.jba b/test/testdata/parser_testdata/missing_parameter.jba new file mode 100644 index 0000000..ce05549 --- /dev/null +++ b/test/testdata/parser_testdata/missing_parameter.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + :"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_parameter_value.jba b/test/testdata/parser_testdata/missing_parameter_value.jba new file mode 100644 index 0000000..584c01c --- /dev/null +++ b/test/testdata/parser_testdata/missing_parameter_value.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command: + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_percent_signs.jba b/test/testdata/parser_testdata/missing_percent_signs.jba new file mode 100644 index 0000000..9b36aec --- /dev/null +++ b/test/testdata/parser_testdata/missing_percent_signs.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + test = "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_quotation_marks.jba b/test/testdata/parser_testdata/missing_quotation_marks.jba new file mode 100644 index 0000000..2e50145 --- /dev/null +++ b/test/testdata/parser_testdata/missing_quotation_marks.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command: test + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_stepid.jba b/test/testdata/parser_testdata/missing_stepid.jba new file mode 100644 index 0000000..5f3b508 --- /dev/null +++ b/test/testdata/parser_testdata/missing_stepid.jba @@ -0,0 +1,5 @@ +{ + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_stepid_nested.jba b/test/testdata/parser_testdata/missing_stepid_nested.jba new file mode 100644 index 0000000..9bf274e --- /dev/null +++ b/test/testdata/parser_testdata/missing_stepid_nested.jba @@ -0,0 +1,11 @@ +{ + id:"testmodul" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_variable.jba b/test/testdata/parser_testdata/missing_variable.jba new file mode 100644 index 0000000..847e9fa --- /dev/null +++ b/test/testdata/parser_testdata/missing_variable.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + = "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_variable_equal_sign.jba b/test/testdata/parser_testdata/missing_variable_equal_sign.jba new file mode 100644 index 0000000..6eb84ce --- /dev/null +++ b/test/testdata/parser_testdata/missing_variable_equal_sign.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_variable_name.jba b/test/testdata/parser_testdata/missing_variable_name.jba new file mode 100644 index 0000000..28e35ec --- /dev/null +++ b/test/testdata/parser_testdata/missing_variable_name.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %% = "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/missing_variable_value.jba b/test/testdata/parser_testdata/missing_variable_value.jba new file mode 100644 index 0000000..59fa41f --- /dev/null +++ b/test/testdata/parser_testdata/missing_variable_value.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% = + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multi_invalid_empty.jba b/test/testdata/parser_testdata/multi_invalid_empty.jba new file mode 100644 index 0000000..6180dbc --- /dev/null +++ b/test/testdata/parser_testdata/multi_invalid_empty.jba @@ -0,0 +1,5 @@ +{ + id:"testmodule" + module:"Script" + script:`` +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multi_invalid_eof.jba b/test/testdata/parser_testdata/multi_invalid_eof.jba new file mode 100644 index 0000000..1215258 --- /dev/null +++ b/test/testdata/parser_testdata/multi_invalid_eof.jba @@ -0,0 +1,10 @@ +{ + id:"testmodule" + module:"Script" + script:`meinscript + hallo1 + hallo2 + hallo3 + hallo4 + hallo5 + hallo6 \ No newline at end of file diff --git a/test/testdata/parser_testdata/multi_invalid_one_line_text_after_end.jba b/test/testdata/parser_testdata/multi_invalid_one_line_text_after_end.jba new file mode 100644 index 0000000..70e7ab9 --- /dev/null +++ b/test/testdata/parser_testdata/multi_invalid_one_line_text_after_end.jba @@ -0,0 +1,5 @@ +{ + id:"testmodule" + module:"Script" + script:`meinscript`dwa +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multi_invalid_quotes_instead_of_ticks.jba b/test/testdata/parser_testdata/multi_invalid_quotes_instead_of_ticks.jba new file mode 100644 index 0000000..63a8524 --- /dev/null +++ b/test/testdata/parser_testdata/multi_invalid_quotes_instead_of_ticks.jba @@ -0,0 +1,8 @@ +{ + id:"testmodule" + module:"Script" + script:"meinscript + wdadwafd + test123 + " +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multi_invalid_text_after_end.jba b/test/testdata/parser_testdata/multi_invalid_text_after_end.jba new file mode 100644 index 0000000..cec6d69 --- /dev/null +++ b/test/testdata/parser_testdata/multi_invalid_text_after_end.jba @@ -0,0 +1,8 @@ +{ + id:"testmodule" + module:"Script" + script:`meinscript + wdadwafd + test123 + `dwa +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multi_valid.jba b/test/testdata/parser_testdata/multi_valid.jba new file mode 100644 index 0000000..183419d --- /dev/null +++ b/test/testdata/parser_testdata/multi_valid.jba @@ -0,0 +1,14 @@ +{ + id:"testmodule" + module:"Script" + script:`meinscript + hallo1 + hallo2 + hallo3 + hallo4 + hallo5 + hallo6 + hallo7 + hallo8 + ` +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multi_valid_one_line.jba b/test/testdata/parser_testdata/multi_valid_one_line.jba new file mode 100644 index 0000000..bb45aa3 --- /dev/null +++ b/test/testdata/parser_testdata/multi_valid_one_line.jba @@ -0,0 +1,5 @@ +{ + id:"testmodule" + module:"Script" + script:`meinscript` +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_basic_modules.jba b/test/testdata/parser_testdata/multiple_basic_modules.jba new file mode 100644 index 0000000..f3d1e2a --- /dev/null +++ b/test/testdata/parser_testdata/multiple_basic_modules.jba @@ -0,0 +1,18 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_basic_modules_nested.jba b/test/testdata/parser_testdata/multiple_basic_modules_nested.jba new file mode 100644 index 0000000..1ba9391 --- /dev/null +++ b/test/testdata/parser_testdata/multiple_basic_modules_nested.jba @@ -0,0 +1,60 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, + }, + { + id:"testmodule3" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + id:"testmodule4" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + id:"testmodule5" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, + { + id:"testmodule6" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, + }, + }, +}, +{ + id:"testmodule7" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + id:"testmodule8" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + id:"testmodule9" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_global.jba b/test/testdata/parser_testdata/multiple_global.jba new file mode 100644 index 0000000..c0c3afb --- /dev/null +++ b/test/testdata/parser_testdata/multiple_global.jba @@ -0,0 +1,32 @@ +{ + %g_test1% = "test" + %g_test2% = "test" + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %g_test3% = "test" + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %g_test4% = "test" + %g_test5% = "test" + %g_test6% = "test" + %test1% = "test" + id:"testmodule3" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %test2% = "test" + id:"testmodule4" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_global_wrong_order1.jba b/test/testdata/parser_testdata/multiple_global_wrong_order1.jba new file mode 100644 index 0000000..fd2eb1f --- /dev/null +++ b/test/testdata/parser_testdata/multiple_global_wrong_order1.jba @@ -0,0 +1,32 @@ +{ + %g_test1% = "test" + %g_test2% = "test" + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %g_test3% = "test" + %g_test4% = "test" + %g_test5% = "test" + %test1% = "test" + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %test2% = "test" + id:"testmodule3" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %g_test6% = "test" + id:"testmodule4" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_global_wrong_order2.jba b/test/testdata/parser_testdata/multiple_global_wrong_order2.jba new file mode 100644 index 0000000..33af3eb --- /dev/null +++ b/test/testdata/parser_testdata/multiple_global_wrong_order2.jba @@ -0,0 +1,30 @@ +{ + %g_test1% = "test" + %g_test2% = "test" + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %g_test3% = "test" + %g_test4% = "test" + %g_test5% = "test" + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + id:"testmodule3" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %g_test6% = "test" + id:"testmodule4" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_module_declaration.jba b/test/testdata/parser_testdata/multiple_module_declaration.jba new file mode 100644 index 0000000..85c2b29 --- /dev/null +++ b/test/testdata/parser_testdata/multiple_module_declaration.jba @@ -0,0 +1,8 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + module:"FileContent" + file:"test" +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_param_false.jba b/test/testdata/parser_testdata/multiple_param_false.jba new file mode 100644 index 0000000..2cf7632 --- /dev/null +++ b/test/testdata/parser_testdata/multiple_param_false.jba @@ -0,0 +1,8 @@ +{ + id:"test" + requireselevatedprivileges: "false" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges: "false" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_param_true.jba b/test/testdata/parser_testdata/multiple_param_true.jba new file mode 100644 index 0000000..c5180c5 --- /dev/null +++ b/test/testdata/parser_testdata/multiple_param_true.jba @@ -0,0 +1,8 @@ +{ + id:"test" + requireselevatedprivileges: "true" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges: "true" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_params1.jba b/test/testdata/parser_testdata/multiple_params1.jba new file mode 100644 index 0000000..def6636 --- /dev/null +++ b/test/testdata/parser_testdata/multiple_params1.jba @@ -0,0 +1,8 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + desc:"test1" + description:"test2" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_params2.jba b/test/testdata/parser_testdata/multiple_params2.jba new file mode 100644 index 0000000..a901dae --- /dev/null +++ b/test/testdata/parser_testdata/multiple_params2.jba @@ -0,0 +1,8 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test1" + grep:"test" + command:"test2" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_params3.jba b/test/testdata/parser_testdata/multiple_params3.jba new file mode 100644 index 0000000..7a14184 --- /dev/null +++ b/test/testdata/parser_testdata/multiple_params3.jba @@ -0,0 +1,10 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + grep:"test1" + module:"FileContent" + file:"test" + grep:"test2" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_params4.jba b/test/testdata/parser_testdata/multiple_params4.jba new file mode 100644 index 0000000..6e11d69 --- /dev/null +++ b/test/testdata/parser_testdata/multiple_params4.jba @@ -0,0 +1,8 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"" + desc:"test" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_params5.jba b/test/testdata/parser_testdata/multiple_params5.jba new file mode 100644 index 0000000..38f29cf --- /dev/null +++ b/test/testdata/parser_testdata/multiple_params5.jba @@ -0,0 +1,7 @@ +{ + id:"" + module:"ExecuteCommand" + command:"test" + stepid:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_stepid.jba b/test/testdata/parser_testdata/multiple_stepid.jba new file mode 100644 index 0000000..ebb3447 --- /dev/null +++ b/test/testdata/parser_testdata/multiple_stepid.jba @@ -0,0 +1,13 @@ +{ + id:"testmodul" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, + +{ + id:"testmodul" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_stepid_declaration.jba b/test/testdata/parser_testdata/multiple_stepid_declaration.jba new file mode 100644 index 0000000..4b3d686 --- /dev/null +++ b/test/testdata/parser_testdata/multiple_stepid_declaration.jba @@ -0,0 +1,7 @@ +{ + id:"testmodul" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + id:"testmodul1" +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/multiple_stepid_nested.jba b/test/testdata/parser_testdata/multiple_stepid_nested.jba new file mode 100644 index 0000000..be1e99b --- /dev/null +++ b/test/testdata/parser_testdata/multiple_stepid_nested.jba @@ -0,0 +1,12 @@ +{ + id:"testmodul" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + id:"testmodul" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/name_alias.jba b/test/testdata/parser_testdata/name_alias.jba new file mode 100644 index 0000000..d23015a --- /dev/null +++ b/test/testdata/parser_testdata/name_alias.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"perms" + path:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/nested_global1.jba b/test/testdata/parser_testdata/nested_global1.jba new file mode 100644 index 0000000..b460281 --- /dev/null +++ b/test/testdata/parser_testdata/nested_global1.jba @@ -0,0 +1,13 @@ +{ + %g_test% = "test" + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/nested_global2.jba b/test/testdata/parser_testdata/nested_global2.jba new file mode 100644 index 0000000..5ce9894 --- /dev/null +++ b/test/testdata/parser_testdata/nested_global2.jba @@ -0,0 +1,13 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + { + %g_test% = "test" + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") + }, +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/non_global_use_global.jba b/test/testdata/parser_testdata/non_global_use_global.jba new file mode 100644 index 0000000..0e61b61 --- /dev/null +++ b/test/testdata/parser_testdata/non_global_use_global.jba @@ -0,0 +1,14 @@ +{ + %g_test% = "test" + id:"testmodule1" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, +{ + %test% = %g_test% + id:"testmodule2" + module:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/one_quotation_mark_only.jba b/test/testdata/parser_testdata/one_quotation_mark_only.jba new file mode 100644 index 0000000..e62a6d4 --- /dev/null +++ b/test/testdata/parser_testdata/one_quotation_mark_only.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command: " + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/opening_brace_only.jba b/test/testdata/parser_testdata/opening_brace_only.jba new file mode 100644 index 0000000..81750b9 --- /dev/null +++ b/test/testdata/parser_testdata/opening_brace_only.jba @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/test/testdata/parser_testdata/overwrite_env_variable.jba b/test/testdata/parser_testdata/overwrite_env_variable.jba new file mode 100644 index 0000000..d5f07ba --- /dev/null +++ b/test/testdata/parser_testdata/overwrite_env_variable.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %result% = "test" +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/param_alias.jba b/test/testdata/parser_testdata/param_alias.jba new file mode 100644 index 0000000..7d2fb38 --- /dev/null +++ b/test/testdata/parser_testdata/param_alias.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"FileContent" + datei:"test" + grep:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/parameter_name_only.jba b/test/testdata/parser_testdata/parameter_name_only.jba new file mode 100644 index 0000000..71300a9 --- /dev/null +++ b/test/testdata/parser_testdata/parameter_name_only.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/parameter_value_only.jba b/test/testdata/parser_testdata/parameter_value_only.jba new file mode 100644 index 0000000..1b55142 --- /dev/null +++ b/test/testdata/parser_testdata/parameter_value_only.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + "test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/print.jba b/test/testdata/parser_testdata/print.jba new file mode 100644 index 0000000..bd5ef66 --- /dev/null +++ b/test/testdata/parser_testdata/print.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + print:"das ist eine printausgabe" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/print_invalid.jba b/test/testdata/parser_testdata/print_invalid.jba new file mode 100644 index 0000000..9afe659 --- /dev/null +++ b/test/testdata/parser_testdata/print_invalid.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + print:1+1 + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/redeclared_variable_same_module1.jba b/test/testdata/parser_testdata/redeclared_variable_same_module1.jba new file mode 100644 index 0000000..e5b3483 --- /dev/null +++ b/test/testdata/parser_testdata/redeclared_variable_same_module1.jba @@ -0,0 +1,8 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% = "test" + passed: if ("test") + %test% = "foo" +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/redeclared_variable_same_module2.jba b/test/testdata/parser_testdata/redeclared_variable_same_module2.jba new file mode 100644 index 0000000..d79a71d --- /dev/null +++ b/test/testdata/parser_testdata/redeclared_variable_same_module2.jba @@ -0,0 +1,8 @@ +{ + %test% = "" + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% = "" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/requireselevatedprivileges_false1.jba b/test/testdata/parser_testdata/requireselevatedprivileges_false1.jba new file mode 100644 index 0000000..9058848 --- /dev/null +++ b/test/testdata/parser_testdata/requireselevatedprivileges_false1.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges:"false" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/requireselevatedprivileges_false2.jba b/test/testdata/parser_testdata/requireselevatedprivileges_false2.jba new file mode 100644 index 0000000..7263c0e --- /dev/null +++ b/test/testdata/parser_testdata/requireselevatedprivileges_false2.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges:"0" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/requireselevatedprivileges_false3.jba b/test/testdata/parser_testdata/requireselevatedprivileges_false3.jba new file mode 100644 index 0000000..d7358ce --- /dev/null +++ b/test/testdata/parser_testdata/requireselevatedprivileges_false3.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges:"f" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/requireselevatedprivileges_invalid1.jba b/test/testdata/parser_testdata/requireselevatedprivileges_invalid1.jba new file mode 100644 index 0000000..988fdb2 --- /dev/null +++ b/test/testdata/parser_testdata/requireselevatedprivileges_invalid1.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges: true + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/requireselevatedprivileges_invalid2.jba b/test/testdata/parser_testdata/requireselevatedprivileges_invalid2.jba new file mode 100644 index 0000000..29315be --- /dev/null +++ b/test/testdata/parser_testdata/requireselevatedprivileges_invalid2.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges: "ja" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/requireselevatedprivileges_true1.jba b/test/testdata/parser_testdata/requireselevatedprivileges_true1.jba new file mode 100644 index 0000000..c6a5ff9 --- /dev/null +++ b/test/testdata/parser_testdata/requireselevatedprivileges_true1.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges:"true" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/requireselevatedprivileges_true2.jba b/test/testdata/parser_testdata/requireselevatedprivileges_true2.jba new file mode 100644 index 0000000..15cf938 --- /dev/null +++ b/test/testdata/parser_testdata/requireselevatedprivileges_true2.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges:"1" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/requireselevatedprivileges_true3.jba b/test/testdata/parser_testdata/requireselevatedprivileges_true3.jba new file mode 100644 index 0000000..3e0f83a --- /dev/null +++ b/test/testdata/parser_testdata/requireselevatedprivileges_true3.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + requireselevatedprivileges:"t" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/round_bracket_in_param.jba b/test/testdata/parser_testdata/round_bracket_in_param.jba new file mode 100644 index 0000000..9839093 --- /dev/null +++ b/test/testdata/parser_testdata/round_bracket_in_param.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"te(st" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/round_bracket_in_value.jba b/test/testdata/parser_testdata/round_bracket_in_value.jba new file mode 100644 index 0000000..884fa1a --- /dev/null +++ b/test/testdata/parser_testdata/round_bracket_in_value.jba @@ -0,0 +1,6 @@ +{ + module:"FileContent" + stepid:"1" + file:"test" + %test% = "foo(bar" +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/slashes_in_param.jba b/test/testdata/parser_testdata/slashes_in_param.jba new file mode 100644 index 0000000..af60142 --- /dev/null +++ b/test/testdata/parser_testdata/slashes_in_param.jba @@ -0,0 +1,20 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"te//st" + passed: if ("test") +}, +{ + id:"testmodule2" + module:"Script" + script:`te//st` + passed: if ("test") +}, +{ + id:"testmodule3" + module:"Script" + script:`te//st + t//est + ` + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/slashes_in_param_with_comment.jba b/test/testdata/parser_testdata/slashes_in_param_with_comment.jba new file mode 100644 index 0000000..f734cff --- /dev/null +++ b/test/testdata/parser_testdata/slashes_in_param_with_comment.jba @@ -0,0 +1,20 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"te//st" //test + passed: if ("test") +}, +{ + id:"testmodule2" + module:"Script" + script:`te//st` + passed: if ("test") +}, +{ + id:"testmodule3" + module:"Script" + script:`te//st //test + t//est //test + ` + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/slashes_in_param_with_quotation_mark_in_comment.jba b/test/testdata/parser_testdata/slashes_in_param_with_quotation_mark_in_comment.jba new file mode 100644 index 0000000..a5f0cc7 --- /dev/null +++ b/test/testdata/parser_testdata/slashes_in_param_with_quotation_mark_in_comment.jba @@ -0,0 +1,20 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"te//st" //te"st + passed: if ("test") +}, +{ + id:"testmodule2" + module:"Script" + script:`te//st` //te"st + passed: if ("test") +}, +{ + id:"testmodule3" + module:"Script" + script:`te//st //te"st + t//est //t"est + ` + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/slashes_in_value.jba b/test/testdata/parser_testdata/slashes_in_value.jba new file mode 100644 index 0000000..97af523 --- /dev/null +++ b/test/testdata/parser_testdata/slashes_in_value.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% = "te//st" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/slashes_in_value_with_comment.jba b/test/testdata/parser_testdata/slashes_in_value_with_comment.jba new file mode 100644 index 0000000..e0e03cc --- /dev/null +++ b/test/testdata/parser_testdata/slashes_in_value_with_comment.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% = "te//st" //test + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/slashes_in_value_with_quotation_mark_in_comment.jba b/test/testdata/parser_testdata/slashes_in_value_with_quotation_mark_in_comment.jba new file mode 100644 index 0000000..3a333b9 --- /dev/null +++ b/test/testdata/parser_testdata/slashes_in_value_with_quotation_mark_in_comment.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% = "te//st" //te"st + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/variable_name_only.jba b/test/testdata/parser_testdata/variable_name_only.jba new file mode 100644 index 0000000..9305f1e --- /dev/null +++ b/test/testdata/parser_testdata/variable_name_only.jba @@ -0,0 +1,7 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + command:"test" + %test% + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/whitespace_in_module_name.jba b/test/testdata/parser_testdata/whitespace_in_module_name.jba new file mode 100644 index 0000000..bcc6251 --- /dev/null +++ b/test/testdata/parser_testdata/whitespace_in_module_name.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + mod ule:"ExecuteCommand" + command:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/parser_testdata/whitespace_in_param_name.jba b/test/testdata/parser_testdata/whitespace_in_param_name.jba new file mode 100644 index 0000000..292673a --- /dev/null +++ b/test/testdata/parser_testdata/whitespace_in_param_name.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module:"ExecuteCommand" + com mand:"test" + passed: if ("test") +}, \ No newline at end of file diff --git a/test/testdata/validator_testdata/existing_variable.jba b/test/testdata/validator_testdata/existing_variable.jba new file mode 100644 index 0000000..c32983f --- /dev/null +++ b/test/testdata/validator_testdata/existing_variable.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module: "ExecuteCommand" + command: "hostname" + %testvar% = "value" +}, \ No newline at end of file diff --git a/test/testdata/validator_testdata/multiple_non_existing_variables.jba b/test/testdata/validator_testdata/multiple_non_existing_variables.jba new file mode 100644 index 0000000..869d006 --- /dev/null +++ b/test/testdata/validator_testdata/multiple_non_existing_variables.jba @@ -0,0 +1,18 @@ +{ + id:"testmodule" + module: "ExecuteCommand" + command: "hostname" + %testvar% = %testvar1% + { + id:"testmodule1" + module: "ExecuteCommand" + command: "hostname" + %testvar2% = %testvar% + { + id:"testmodule2" + module: "ExecuteCommand" + command: "hostname" + %testvar3% = %testvar2% + }, + }, +}, \ No newline at end of file diff --git a/test/testdata/validator_testdata/multiple_variables.jba b/test/testdata/validator_testdata/multiple_variables.jba new file mode 100644 index 0000000..6b59070 --- /dev/null +++ b/test/testdata/validator_testdata/multiple_variables.jba @@ -0,0 +1,18 @@ +{ + id:"testmodule" + module: "ExecuteCommand" + command: "hostname" + %testvar% = "value" + { + id:"testmodule1" + module: "ExecuteCommand" + command: "hostname" + %testvar1% = %testvar% + { + id:"testmodule2" + module: "ExecuteCommand" + command: "hostname" + %testvar2% = %testvar1% + }, + }, +}, \ No newline at end of file diff --git a/test/testdata/validator_testdata/non_existing_variable.jba b/test/testdata/validator_testdata/non_existing_variable.jba new file mode 100644 index 0000000..94a17d1 --- /dev/null +++ b/test/testdata/validator_testdata/non_existing_variable.jba @@ -0,0 +1,6 @@ +{ + id:"testmodule" + module: "ExecuteCommand" + command: "hostname" + %testvar% = %idontexist% +}, \ No newline at end of file diff --git a/test/testdata/windows_utility_testdata/secedit.cfg b/test/testdata/windows_utility_testdata/secedit.cfg new file mode 100644 index 0000000000000000000000000000000000000000..00111c99e933bde71518dd97180516c9406ef981 GIT binary patch literal 16410 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 literal 0 HcmV?d00001 diff --git a/test/windows_utility_test/auditpolicyquery_test.go b/test/windows_utility_test/auditpolicyquery_test.go new file mode 100644 index 0000000..5302a19 --- /dev/null +++ b/test/windows_utility_test/auditpolicyquery_test.go @@ -0,0 +1,19 @@ +package windows_utility + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "testing" +) + +func TestAuditPolicyQuery(t *testing.T) { + result := handler.AuditPolicyQuery(models.ParameterMap{"guid": "{0CCE9214-69AE-11D9-BED3-505054503030}"}) + + if result.Err != nil { + t.Error(result.Err) + } + + // Basiert auf Systemsprache + if result.Result != "Erfolg und Fehler" && result.Result != "Success and Failure" { + t.Error("Fehlgeschlagen:", result.Result) + } +} diff --git a/test/windows_utility_test/regquery_test.go b/test/windows_utility_test/regquery_test.go new file mode 100644 index 0000000..3b51e24 --- /dev/null +++ b/test/windows_utility_test/regquery_test.go @@ -0,0 +1,40 @@ +package windows_utility_test + +import ( + "fmt" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "testing" +) + +func TestInt(t *testing.T) { + // Typ: REG_DWORD, REG_QWORD + res, err := util.RegQuery(`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters`, "MaximumPasswordAge") + if res == "30" { + fmt.Println(res) + } else { + t.Error("Fehlgeschlagen:", res, err) + } +} + +func TestMultiString(t *testing.T) { + // Typ: REG_MULTI_SZ + res, err := util.RegQuery(`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MSiSCSI`, "RequiredPrivileges") + should := "SeAuditPrivilege,SeChangeNotifyPrivilege,SeCreateGlobalPrivilege,SeCreatePermanentPrivilege,SeImpersonatePrivilege,SeTcbPrivilege,SeLoadDriverPrivilege," + if err != static.ERROR_VALUE_NOT_FOUND && res == should { + fmt.Println(res) + } else { + t.Error("Fehlgeschlagen:", res, err) + } +} + +func TestString(t *testing.T) { + // Typ: REG_SZ, REG_EXPAND_SZ + res, err := util.RegQuery(`HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MSiSCSI`, "Group") + should := "iSCSI" + if err != static.ERROR_VALUE_NOT_FOUND && res == should { + fmt.Println(res) + } else { + t.Error("Fehlgeschlagen:", res, err) + } +} diff --git a/test/windows_utility_test/secquery_test.go b/test/windows_utility_test/secquery_test.go new file mode 100644 index 0000000..af9a479 --- /dev/null +++ b/test/windows_utility_test/secquery_test.go @@ -0,0 +1,53 @@ +package windows_utility + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/modules" + "os" + "testing" +) + +var ( + gopath = os.Getenv("gopath") + `\src\github.com\Jungbusch-Softwareschmiede\jungbusch-auditorium\` + handler = modules.MethodHandler{} +) + +func TestDump(t *testing.T) { + result := handler.DumpSecuritySettings(models.ParameterMap{"path": gopath + `test\testdata\windows_utility_testdata\`}) + if result.Err != nil { + t.Error(result.Err) + } +} + +func TestSecQuery(t *testing.T) { + result := handler.SecuritySettingsQuery(models.ParameterMap{"path": gopath + `test\testdata\windows_utility_testdata\\secedit.cfg`, "valueName": "PasswordComplexity"}) + + if result.Err != nil { + t.Error(result.Err) + } + + if result.Result != "0" { + t.Error("Fehlgeschlagen:", result.Result) + } +} + +func TestSecQueryQuotationMarks(t *testing.T) { + result := handler.SecuritySettingsQuery(models.ParameterMap{"path": gopath + `test\testdata\windows_utility_testdata\secedit.cfg`, "valueName": "NewAdministratorName"}) + + if result.Err != nil { + t.Error(result.Err) + } + + if result.Result != "Administrator" { + t.Error("Fehlgeschlagen:", result.Result) + } +} + +func TestSecQuerySID(t *testing.T) { + result := handler.SecuritySettingsQuery(models.ParameterMap{"path": gopath + `test\testdata\windows_utility_testdata\secedit.cfg`, "valueName": "SeBackupPrivilege"}) + + // SID: Administratoren, Sicherungsoperatoren + if result.Result != "*S-1-5-32-544,*S-1-5-32-551" { + t.Error("Fehlgeschlagen:", result.Result) + } +} diff --git a/util/logger/logger.go b/util/logger/logger.go new file mode 100644 index 0000000..4cfcd88 --- /dev/null +++ b/util/logger/logger.go @@ -0,0 +1,346 @@ +// Dieses package übernimmt das Schreiben der Log-Datei, das Ausgeben von Informationen auf der Konsole, +// sowie das Behandeln von Error-Nachrichten und das Beenden des Programms. +package logger + +import ( + "bufio" + "fmt" + . "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/permissions" + "github.com/pkg/errors" + "io/fs" + "log" + "os" + "path/filepath" + "runtime" + "strconv" + s "strings" + "time" +) + +// Das Logger-Objekt, welches im Hintergrund die Log-Datei schreibt +type logger struct { + cmdVerbosity int // Die Commandline-Stufe + logVerbosity int // Die Log-Stufe + *log.Logger // Der Logger selbst + wait bool // True, wenn nach beenden des Programms gewartet werden soll + file *os.File // Die Log-Datei +} + +// In diesem struct können die verfügbaren Log-Level gespeichert werden +type level struct { + name string + lv int +} + +var ( + l logger + info level + warn level + erro level + debug level + none level +) + +// Initialisiert alle für die Verwendung des Loggers intern benötigten Werte +func InitializeLogger(cs *ConfigStruct, preLog []LogMsg) error { + // Verbosity validieren damit wir so schnell wie möglich den Logger initialisieren können + if cs.VerbosityConsole < 0 || cs.VerbosityConsole > 4 || cs.VerbosityLog < 0 || cs.VerbosityLog > 4 { + return errors.New("Bitte ein Loglevel zwischen 0 und 4 angeben.") + } + + // Log-Pfad generieren + path, err := util.GetAbsolutePath(cs.OutputPath + static.PATH_SEPERATOR + static.LOG_NAME + ".log") + + // Datei öffnen + l.file, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, static.CREATE_FILE_PERMISSIONS) + if err != nil { + return err + } + + // Log-Objekt initialisieren + l = logger{ + cmdVerbosity: cs.VerbosityConsole, + logVerbosity: cs.VerbosityLog, + wait: cs.KeepConsoleOpen, + file: l.file, + Logger: log.New(l.file, "", log.Ltime), + } + + // Verfügbare Log-Level initialisieren + none = level{name: "", lv: 0} + erro = level{name: "ERROR ", lv: 1} + warn = level{name: "WARNING ", lv: 2} + info = level{name: "INFO ", lv: 3} + debug = level{name: "DEBUG ", lv: 4} + + // Die Log-Nachrichten, welche vor Initialisierung des Loggers gesammelt wurden ausgeben + for n := range preLog { + processPrelogMsg(preLog[n]) + } + + return nil +} + +// Schließt den Logger +func CloseLogger() { + err := l.file.Close() + if err != nil { + fmt.Println("Error beim schließen des Loggers: " + err.Error()) + } +} + +// Diese Funktion wird dann aufgerufen, wenn vor Initialisierung des Loggers ein Fehler aufgetreten ist. +// Es wird versucht eine Log-Datei an unterschiedlichen Pfaden zu erstellen, bis dies erfolgreich ist. +func LogPanic(cs *ConfigStruct, preLog []LogMsg) { + path, err := panicExit(cs, preLog) + if err != nil { + fmt.Println(panicExit(cs, preLog)) + fmt.Println("Das Jungbusch-Auditorium konnte keinen Log erstellen.\nDas Programm beendet nun.") + Exit() + } else { + fmt.Println("Vor der initialisierung des Loggers ist ein Fehler aufgetreten. Eine Log-Datei wurde an folgendem Pfad erstellt: " + path) + } +} + +// Initialisiert den Output-Ordner. In diesem wird die Log-Datei abgelegt. +func InitializeOutput(cs *ConfigStruct) (err error) { + // Pfad des Output-Ordners zusammenbauen + if cs.OutputPath, err = util.GetAbsolutePath(cs.OutputPath + static.PATH_SEPERATOR + static.OUTPUT_FOLDER_NAME + "_" + time.Now().Format(static.OUTPUT_TIMESTAMP_FORMAT)); err != nil { + return errors.New("Unbekannter Fehler beim initialisieren des Output-Ordners: " + err.Error()) + } + + // Output-Ordner erstellen + if err = os.Mkdir(cs.OutputPath, static.CREATE_DIRECTORY_PERMISSIONS); err != nil { + // Wenn der Ordner bereits existiert, wurden mehrere JBA-Instanzen zur selben Sekunde gestartet. + // Wir lassen uns nicht anmerken dass irgendwas schief gelaufen ist und tun einfach so, + // als wäre das ein Fehler den wir von Beginn an abfangen wollten ;) + if errors.Is(err, fs.ErrExist) { + return errors.New("Bitte nur jeweils eine Instanz der Jungbusch-Softwareschmiede starten.") + } else { + return errors.New("Fehler beim erstellen des Output-Pfads: " + err.Error()) + } + } + + return +} + +// Gibt eine Nachricht auf dem Info-Level aus. Wird always auf true gesetzt, wird die Nachricht auf der Konsole +// unabhängig vom Log-Level ausgegeben. +func InfoPrint(msg string, always bool) { + logMe(msg, info, always) +} + +// Gibt eine Nachricht auf dem Info-Level aus. Die Nachricht auf der Konsole wird unabhängig vom Log-Level ausgegeben. +func InfoPrintAlways(msg string) { + logMe(msg, info, true) +} + +// Gibt eine Nachricht auf dem Info-Level aus. +func Info(msg string) { + logMe(msg, info, false) +} + +// Gibt eine Nachricht auf dem Warn-Level aus. +func Warn(msg string) { + logMe(msg, warn, false) +} + +// Gibt eine Nachricht auf dem Error-Level aus. +func Err(msg string) { + logMe(msg, erro, false) +} + +// Gibt eine Nachricht auf dem Error-Level aus und beendet das Programm. +func ErrAndExit(msg string) { + logMe(msg, erro, false) + Exit() +} + +// Gibt den übergebenen Error aus und beendet das Programm. Unterscheidet zwischen einem herkömmlichen Error +// und einem SyntaxError-Objekt. +func HandleError(err error) { + if err != nil { + exitString := "" + Err(Seperate()) + + switch err.(type) { + case *SyntaxError: + syntaxErr := err.(*SyntaxError) + Err(syntaxErr.ErrorMsg) + Err(createSyntaxErrorString(syntaxErr.Errorkeyword, syntaxErr.LineNo, syntaxErr.Line)) + + default: + Err(err.Error()) + } + + ErrAndExit(exitString) + } +} + +// Gibt eine Nachricht auf dem Debug-Level aus. +func Debug(msg string) { + logMe(msg, debug, false) +} + +// Löscht den Temp-Ordner und beendet das Programm. +func Exit() { + // Temp-Ordner löschen + if runtime.GOOS == "windows" && static.TempPath != "" { + _ = os.RemoveAll(static.TempPath) + } + + if l != (logger{}) { + if l.wait { + reader := bufio.NewReader(os.Stdin) + fmt.Print("--- Enter drücken um fortzufahren ---") + _, _, _ = reader.ReadLine() + } + os.Exit(1) + } else { + os.Exit(1) + } +} + +// Der übergebene String wird zwischen Linien gesetzt +func SeperateTitle(in string) string { + return "—————————————————————————————————" + in + "—————————————————————————————————" +} + +// Returned eine Linie +func Seperate() string { + return "——————————————————————————————————————————————————————————————————" +} + +// Übernimmt die Logik des Ausgeben der Nachrichten, sowie die Progressbar +func logMe(msg string, verbosity level, alwaysPrint bool) { + if static.ProgressBar != nil { + _ = static.ProgressBar.Clear() + } + + if msg != "" { + l.SetPrefix(verbosity.name) + + if l.cmdVerbosity >= verbosity.lv { + fmt.Println(verbosity.name + time.Now().Format("15:04:05") + " - " + msg) + } else if alwaysPrint { + fmt.Print(" ") + fmt.Println(msg) + } + + if l.logVerbosity >= verbosity.lv { + l.Logger.Println("- " + msg) + } + } + + if static.ProgressBar != nil { + _ = static.ProgressBar.RenderBlank() + } +} + +// Hält die Logik für die LogPanic-Methode +func panicExit(cs *ConfigStruct, preLog []LogMsg) (string, error) { + // Ziel: Finden eines Pfads, an den wir einen Log schreiben können + + // 1. Versuch: Pfad der Executable + r, w, isDir, err := permissions.Permission(".") + if err == nil && r && w && isDir { + // Hier kann kein err auftreten, das checkt permissions.Permission bereits + path, _ := util.GetAbsolutePath(".") + cs.OutputPath = path + err = InitializeLogger(cs, preLog) + + // Wenn der err nicht nil ist, machen wir weiter + if err == nil { + return path, nil + } + } + + // 2. Versuch: Working-Directory + pfad, err := filepath.Abs(".") + if err == nil { + r, w, isDir, err = permissions.Permission(pfad) + if err == nil && r && w && isDir { + cs.OutputPath = pfad + err = InitializeLogger(cs, preLog) + + // Wenn der err nicht nil ist, machen wir weiter + if err == nil { + return pfad, nil + } + } + } + + // 3. Versuch: System-spezifische Log-Pfade + if runtime.GOOS == "windows" { + cs.OutputPath = static.TempPath + err = InitializeLogger(cs, preLog) + + // Wenn der err nicht nil ist, machen wir weiter + if err == nil { + return pfad, nil + } + } else { + pfad = "/var/log/JungbuschAuditorium" + err = os.Mkdir(pfad, static.CREATE_DIRECTORY_PERMISSIONS) + + cs.OutputPath = pfad + err = InitializeLogger(cs, preLog) + + // Wenn der err nicht nil ist, machen wir weiter + if err == nil { + return pfad, nil + } + } + + return "", errors.New("Alle Log-Optionen fehlgeschlagen: " + err.Error()) +} + +// Diese Methode erstellt einen Error-String, der einen benutzerdefinierte Nachricht, sowie, wenn möglich, die genaue Position des Errors ausgiebt. +func createSyntaxErrorString(keyword string, lineNumber int, line string) string { + var errorString string + + if lineNumber != -1 { + index := s.Index(line, keyword) + errorString = "Zeile " + strconv.Itoa(lineNumber) + ": " + line + upIndex := (len(errorString) - len(line)) + index + (len(keyword) / 2) + 20 + + errorString += "\n" + fmt.Sprintf("%"+strconv.Itoa(upIndex)+"v", "") + "^" + } else { + if len(line) > 0 { + errorString = line + } else { + errorString = "" + } + } + + return errorString +} + +// Verarbeitet die übergebenen LogMsg-Objekte des PreLoggers. Wenn die Nachricht vom Typ Error ist, wird sie ausgegeben +// und das Programm beendet, ansonsten wird sie wie gewohnt auf dem korrekten Level ausgegeben. +func processPrelogMsg(msg LogMsg) { + if msg.Level == 1 { + ErrAndExit(msg.Message) + } else { + logMe(msg.Message, getLevel(msg.Level), msg.AlwaysPrint) + } +} + +// Mappt einem Level vom Typ int ein level-Objekt zu. +func getLevel(lvl int) level { + switch lvl { + case 1: + return erro + case 2: + return warn + case 3: + return info + case 4: + return debug + default: + return none + } +} diff --git a/util/permissions/unix_permissions.go b/util/permissions/unix_permissions.go new file mode 100644 index 0000000..f0e2622 --- /dev/null +++ b/util/permissions/unix_permissions.go @@ -0,0 +1,49 @@ +// +build linux darwin + +// Erlaubt das bestimmen von Datei- oder Directory-Permissions basierend auf den Berechtigungen des aktuellen Prozesses +package permissions + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/pkg/errors" + "golang.org/x/sys/unix" + "os" + "path/filepath" +) + +// Überprüft die Read/Write-Rechte des ausführenden Users einer Datei oder Directory +func Permission(in string) (read bool, write bool, isDir bool, err error) { + // Pfad bereinigen + path := filepath.Clean(filepath.ToSlash(in)) + if !filepath.IsAbs(in) { + in, err = filepath.Abs(filepath.Dir(os.Args[0]) + static.PATH_SEPERATOR + filepath.Clean(in)) + if err != nil { + return + } + } + + // Prüfen, ob Datei/Verzeichnis vorhanden ist + var info os.FileInfo + if info, err = os.Stat(path); err != nil { + return + } else { + if info == nil { + err = errors.New("Unbekannter Fehler!") + return + } else { + // Prüfen, ob es ein Verzeichnis oder eine Datei ist + isDir = info.IsDir() + } + + // Prüfen, ob Lesen möglich ist + if err = unix.Access(path, unix.R_OK); err == nil { + read = true + } + // Prüfen, ob Schreiben möglich ist + if err = unix.Access(path, unix.W_OK); err == nil { + write = true + } + } + + return +} diff --git a/util/permissions/windows_permissions.go b/util/permissions/windows_permissions.go new file mode 100644 index 0000000..fdb3181 --- /dev/null +++ b/util/permissions/windows_permissions.go @@ -0,0 +1,128 @@ +// +build windows + +package permissions + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/pkg/errors" + "io/fs" + "io/ioutil" + "os" + "path/filepath" +) + +// Überprüft die Read/Write-Rechte des ausführenden Users einer Datei oder Directory +func Permission(in string) (read bool, write bool, isDir bool, err error) { + // Pfad bereinigen + in = filepath.Clean(filepath.ToSlash(in)) + if !filepath.IsAbs(in) { + in, err = filepath.Abs(filepath.Dir(os.Args[0]) + static.PATH_SEPERATOR + filepath.Clean(in)) + if err != nil { + return + } + } + + // Herausfinden, ob der übergebene Pfad eine Directory ist + info, err := os.Stat(in) + if err != nil { + return + } + isDir = info.IsDir() + + if isDir { + read, write, err = getDirectoryPerms(in) + } else { + read, write, err = getFilePerms(in) + } + return +} + +// Überprüft die Permissions des ausführenden Users einer File +func getFilePerms(file string) (read bool, write bool, err error) { + // Rechte der Directory getten + var r bool + r, err = directoryHasRead(filepath.Dir(file)) + + // Wenn wir keine read-Rechte auf der Directory haben, dann können wir die File weder lesen, noch schreiben + if err != nil || !r { + return false, false, nil + } + + // Wenn die Rechte der Directory stimmen, checken wir nun die Rechte der File + read, err = fileHasRead(file) + write, err = fileHasWrite(file) + return +} + +// Überprüft die Permissions des ausführenden Users einer Directory +// Der User kann read-, aber keine write-Rechte haben - oder andersrum +func getDirectoryPerms(dir string) (read bool, write bool, err error) { + // Pfad bereinigen + in := filepath.Clean(filepath.ToSlash(dir + static.PATH_SEPERATOR + "permissiontest")) + if !filepath.IsAbs(in) { + in, err = filepath.Abs(filepath.Dir(os.Args[0]) + static.PATH_SEPERATOR + filepath.Clean(in)) + if err != nil { + return + } + } + + // Read-Permissions überprüfen + read, err = directoryHasRead(dir) + if err != nil { + return + } + + // Read/Write-Permissions testen + write, err = directoryHasWrite(in) + return +} + +func fileHasWrite(in string) (read bool, err error) { + return fileHelper(in, false, os.O_WRONLY) +} + +func fileHasRead(in string) (read bool, err error) { + return fileHelper(in, false, os.O_RDONLY) +} + +// Überprüft ob der aktuelle User read-Berechtigungen für die angegebene Directory hat, indem wir die Directory selbst auslesen +func directoryHasRead(in string) (read bool, err error) { + _, err = ioutil.ReadDir(in) + if err != nil { + if !errors.Is(err, fs.ErrPermission) { + return false, errors.New("Unbekannter Fehler: " + err.Error()) + } else { + return false, nil + } + } + + return true, nil +} + +// Überprüft, ob eine Directory Write-Permissions hat. Es ist möglich dass wir write haben, aber keine read +func directoryHasWrite(in string) (write bool, err error) { + return fileHelper(in, true, os.O_RDWR|os.O_CREATE) +} + +func fileHelper(in string, remove bool, args int) (perm bool, err error) { + var file *os.File + file, err = os.OpenFile(in, args, 0666) + + if err != nil { + // Wenn der err nicht vom Typ "access denied" ist, ist was blödes passiert + if !errors.Is(err, fs.ErrPermission) { + // Wenn Access nicht denied wurde, ist ein unbekannter Fehler aufgetreten. + err = errors.New("Unbekannter Fehler: " + err.Error()) + } else { + return false, nil + } + } else { + perm = true + file.Close() + if remove { + err = os.Remove(in) + } + } + + return +} diff --git a/util/privilege/privilege_detector_unix.go b/util/privilege/privilege_detector_unix.go new file mode 100644 index 0000000..d26405d --- /dev/null +++ b/util/privilege/privilege_detector_unix.go @@ -0,0 +1,21 @@ +// +build linux darwin + +// Überprüft, ob der auszuführende Nutzer Administrator/ Root-Privilegien hat +package privilege + +import ( + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "os" + "syscall" +) + +// Prüfen ob Prozess als root gestartet wurde +func HasRootPrivileges() bool { + if os.Getegid() == 0 { + syscall.Umask(0) + static.CREATE_DIRECTORY_PERMISSIONS = 0767 + static.CREATE_FILE_PERMISSIONS = 0646 + return true + } + return false +} diff --git a/util/privilege/privilege_detector_windows.go b/util/privilege/privilege_detector_windows.go new file mode 100644 index 0000000..879fc75 --- /dev/null +++ b/util/privilege/privilege_detector_windows.go @@ -0,0 +1,11 @@ +// +build windows + +package privilege + +import "os" + +// Prüfen ob Prgramm mit elevated privileges gestartet ist (Ausführen als Administrator) +func HasRootPrivileges() bool { + _, err := os.Open("\\\\.\\PHYSICALDRIVE0") + return err == nil +} diff --git a/util/unix_utility.go b/util/unix_utility.go new file mode 100644 index 0000000..caaca97 --- /dev/null +++ b/util/unix_utility.go @@ -0,0 +1,25 @@ +// +build linux darwin + +package util + +import ( + "errors" + "os/exec" + s "strings" +) + +// Dummy Funktion, wird benötigt da diese Funktion im OS-Detector ausschließlich für Windows aufgerufen wird und Go meckert wenn sie für Unix nicht vorhanden ist. +func RegQuery(keyPath string, value string) (string, error) { + return "", nil +} + +// Führt den übergebenen Befehl aus +func ExecCommand(command string) (string, error) { + var cmd *exec.Cmd + cmd = exec.Command("bash", "-c", command) + out, err := cmd.CombinedOutput() + if err != nil && !s.Contains(command, ">/dev/null") { + return "", errors.New(err.Error() + ": " + string(out)) + } + return string(out), nil +} diff --git a/util/utility.go b/util/utility.go new file mode 100644 index 0000000..e721f73 --- /dev/null +++ b/util/utility.go @@ -0,0 +1,264 @@ +package util + +import ( + "bufio" + "fmt" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/models" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/util/permissions" + "github.com/pkg/errors" + "golang.org/x/text/encoding/unicode" + "golang.org/x/text/transform" + "io/fs" + "os" + "path/filepath" + "strconv" + s "strings" +) + +// Gibt den absoluten Pfad des übergebenen Pfads zurück. Geht vom Pfad der Executable aus. +// Die aktuell working-directory wird ignoriert. +func GetAbsolutePath(currentPath string) (path string, err error) { + path = filepath.ToSlash(currentPath) + if !filepath.IsAbs(currentPath) { + path, err = filepath.Abs(filepath.Dir(os.Args[0]) + static.PATH_SEPERATOR + filepath.Clean(currentPath)) + return path, err + } + return currentPath, nil +} + +// Castet den übergebenen String entweder zu einem Boolean, Float oder String, wenn nichts anderes zutrifft +func CastToAppropriateType(value string) interface{} { + value = s.Trim(value, "\"") + if b, err := strconv.ParseBool(value); err == nil { + return b + } else if num, err := strconv.ParseFloat(value, 64); err == nil { + return num + } else { + return value + } +} + +// Returned true, wenn der Array den übergebenen String enthält +func ArrayContainsString(arr []string, val string) bool { + if arr != nil { + for _, a := range arr { + if s.ToLower(a) == s.ToLower(val) { + return true + } + } + } + return false +} + +// Entfernt alle Leerzeichen in einem String +func CompressString(in string) string { + in = s.Replace(in, " ", "", -1) + return in +} + +// Entfernt alle im Array übergebenen Strings aus dem String +func RemoveFromString(in string, toReplace []string) string { + for n := range toReplace { + in = s.Replace(in, toReplace[n], "", -1) + } + return in +} + +// Liest eine utf-8-Datei ein +func ReadFile(filename string) ([]string, error) { + return readFileWorker(filename, "utf8") +} + +// Liest eine utf16-Datei ein +func ReadUTF16File(filename string) ([]string, error) { + return readFileWorker(filename, "utf16") +} + +// Hilfs-Funktion zum Einlesen von Dateien +func readFileWorker(filename string, mode string) ([]string, error) { + // Pfad in absoluten Pfad konvertieren + filename, err := GetAbsolutePath(filename) + if err != nil { + return nil, err + } + + // Lese-Berechtigungen am angegebenen Pfad bestimmen + r, _, isDir, err := permissions.Permission(filename) + + switch { + case err != nil: + if errors.Is(err, fs.ErrNotExist) { + return nil, errors.New("Die angegebene Datei existiert nicht: " + filename) + } + return nil, errors.New(err.Error()) + + case isDir: + return nil, errors.New("Es muss eine Datei angegeben werden, kein Ordner.") + + case !r: + return nil, errors.New("Dem ausführenden Benutzer fehlen die Lese-Rechte für die Datei am folgenden Pfad: " + filename) + } + + audFile, err := os.Open(filename) + if err != nil { + return nil, err + } + + defer audFile.Close() + + var audLines []string + + var scanner *bufio.Scanner + switch mode { + case "utf16": + scanner = bufio.NewScanner(transform.NewReader(audFile, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder())) + default: + scanner = bufio.NewScanner(audFile) + } + + for scanner.Scan() { + audLines = append(audLines, s.Replace(scanner.Text(), "\t", "", -1)) + } + + if scanner.Err() != nil { + return nil, scanner.Err() + } + + return audLines, nil +} + +// Gibt true zurück, wenn der übergebene Pfad eine Datei ist. +func IsFile(name string) bool { + name, err := GetAbsolutePath(name) + if err != nil { + return false + } + if info, err := os.Stat(name); err == nil { + if info != nil { + if !info.IsDir() { + return true + } + return false + } + return false + } else if os.IsNotExist(err) { + return false + } else { + return false + } +} + +// Gibt true zurück, wenn der übergebene Pfad ein Verzeichniss ist. +func IsDir(name string) bool { + name, err := GetAbsolutePath(name) + if err != nil { + return false + } + if info, err := os.Stat(name); err == nil { + if info != nil { + if info.IsDir() { + return true + } + return false + } + return false + } else if os.IsNotExist(err) { + return false + } else { + return false + } +} + +// Erstellt eine Datei. Ist dieselbe Datei bereits vorhanden, wird sie überschrieben. +func CreateFile(data []string, fileName string) error { + fileName, err := GetAbsolutePath(fileName) + if err != nil { + return err + } + + file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, static.CREATE_FILE_PERMISSIONS) + + if err != nil { + return err + } + + datawriter := bufio.NewWriter(file) + + for _, line := range data { + _, _ = datawriter.WriteString(line + "\n") + } + + datawriter.Flush() + file.Close() + + return nil +} + +// Parsed den Wert eines übergebenen Strings in einen Boolean und gibt diesen zurück. +func ParseStringToBool(in string) (bool, error) { + in = s.TrimSpace(s.ToLower(in)) + + if in == "1" || in == "t" || in == "true" { + return true, nil + } + + if in == "0" || in == "f" || in == "false" { + return false, nil + } + + return false, errors.New("Der Wert für <" + in + "> darf nur sein.") +} + +// Gibt den übergebenen string-Array auf der Konsole aus +func PrintStrArray(in []string) string { + var out string + + for n := range in { + out += in[n] + ", " + } + + if len(out) > 2 { + out = out[:len(out)-2] + } + + return out +} + +// Returned den string aus str der zwischen strings (rem) steht +func GetStringInBetween(str string, rem string) string { + start := s.Index(str, rem) + if start == -1 { + return "" + } + end := s.LastIndex(str, rem) + if end == -1 { + return "" + } + return str[start+1 : end] +} + +// +// +// +// Konsolenausgaben (Debug) +// +// +// +func AuditModulePrinter(mod models.AuditModule, tabs int) { + currTabs := tabs + tabs += 14 + + fmt.Println(fmt.Sprintf("%"+strconv.Itoa(tabs)+"v", "Modul-Name: ") + fmt.Sprintf("%"+strconv.Itoa(len(mod.ModuleName)+3)+"v", mod.ModuleName)) + fmt.Println(fmt.Sprintf("%"+strconv.Itoa(tabs)+"v", "Passed: ") + fmt.Sprintf("%"+strconv.Itoa(len(mod.Passed)+3)+"v", mod.Passed)) + fmt.Print(fmt.Sprintf("%"+strconv.Itoa(tabs)+"v", "Variables: ") + fmt.Sprintf("%"+strconv.Itoa(3)+"v", "")) + fmt.Println(mod.Variables) + fmt.Print(fmt.Sprintf("%"+strconv.Itoa(tabs)+"v", "Modul-Param: ") + fmt.Sprintf("%"+strconv.Itoa(3)+"v", "")) + fmt.Println(mod.ModuleParameters) + + for n := range mod.NestedModules { + fmt.Println() + fmt.Println("-----------Nested") + AuditModulePrinter(mod.NestedModules[n], currTabs+5) + } +} diff --git a/util/windows_utility.go b/util/windows_utility.go new file mode 100644 index 0000000..1ee10cd --- /dev/null +++ b/util/windows_utility.go @@ -0,0 +1,91 @@ +// +build windows + +package util + +import ( + "fmt" + "github.com/Jungbusch-Softwareschmiede/jungbusch-auditorium/static" + "github.com/pkg/errors" + "golang.org/x/sys/windows/registry" + "os/exec" + "strconv" + s "strings" + "syscall" +) + +// Führt einen Befehl aus +func ExecCommand(command string) (string, error) { + var cmd *exec.Cmd + cmd = exec.Command("powershell", "-command", command) + fmt.Println(cmd) + out, err := cmd.CombinedOutput() + + if err != nil { + return "", errors.New(err.Error() + ": " + string(out)) + } + return string(out), nil +} + +// Registry Abfrage ausführen +func RegQuery(keyPath string, value string) (string, error) { + // Rootkey checken + aus keyPath löschen + rootKeys := map[string]registry.Key{ + "HKEY_CLASSES_ROOT": registry.CLASSES_ROOT, + "HKEY_CURRENT_USER": registry.CURRENT_USER, + "HKEY_LOCAL_MACHINE": registry.LOCAL_MACHINE, + "HKEY_USERS": registry.USERS, + "HKEY_CURRENT_CONFIG": registry.CURRENT_CONFIG, + "HKEY_PERFORMANCE_DATA": registry.PERFORMANCE_DATA, + } + + var rootKey registry.Key + for rootKeyString, rootKeyConst := range rootKeys { + if s.Contains(keyPath, rootKeyString) { + rootKey = rootKeyConst + keyPath = s.Replace(keyPath, rootKeyString+"\\", "", 1) + } + } + + // Key öffnen + key, err := registry.OpenKey(rootKey, keyPath, registry.QUERY_VALUE) + if err != nil { + if err == syscall.ERROR_FILE_NOT_FOUND { + return "", static.ERROR_KEY_NOT_FOUND + } else { + return "", err + } + } + defer key.Close() + + // Valuetyp ermitteln + _, valueType, err := key.GetValue(value, []byte{}) + if err != nil { + if err == syscall.ERROR_FILE_NOT_FOUND { + return "", static.ERROR_VALUE_NOT_FOUND + } else { + return "", err + } + } + + // Value je nach Valuetyp zurückgeben + switch valueType { + // REG_SZ + REG_EXPAND_SZ + case 1, 2: + res, _, err := key.GetStringValue(value) + return res, err + // REG_DWORD + REG_DWORD_BIG_ENDIAN + REG_QWORD + case 4, 5, 11: + res, _, err := key.GetIntegerValue(value) + return strconv.Itoa(int(res)), err + // REG_MULTI_SZ + case 7: + res, _, err := key.GetStringsValue(value) + resString := "" + for n := range res { + resString += res[n] + "," + } + return resString, err + // Eventull noch weitere Typen + } + return "", errors.New("Unbekannter Fehler.") +} diff --git a/versioninfo.json b/versioninfo.json new file mode 100644 index 0000000..8467e09 --- /dev/null +++ b/versioninfo.json @@ -0,0 +1,30 @@ +{ + "FixedFileInfo": { + "FileVersion": { + "Major": 1, + "Minor": 0, + "Patch": 0, + "Build": 0 + }, + "FileFlagsMask": "3f", + "FileFlags ": "00", + "FileOS": "040004", + "FileType": "01", + "FileSubType": "00" + }, + "StringFileInfo": { + "CompanyName": "Jungbusch Softwarschmiede", + "FileDescription": "auditorium.jungbusch-softwareschmie.de", + "FileVersion": "V1.0", + "LegalCopyright": "MIT License", + "ProductName": "Auditorium", + "ProductVersion": "V1.0" + }, + "VarFileInfo": { + "Translation": { + "LangID": "0407", + "CharsetID": "04B0" + } + }, + "ManifestPath": "" +} \ No newline at end of file