offlinesec-client 1.0.30__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/PKG-INFO +4 -1
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/README.md +3 -0
- offlinesec_client-1.1.0/offlinesec_client/__init__.py +1 -0
- offlinesec_client-1.1.0/offlinesec_client/abap_system.py +101 -0
- offlinesec_client-1.1.0/offlinesec_client/bo_system.py +31 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/func.py +30 -1
- offlinesec_client-1.1.0/offlinesec_client/java_system.py +56 -0
- offlinesec_client-1.1.0/offlinesec_client/multi_systems.py +50 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/req_java_notes.py +1 -1
- offlinesec_client-1.1.0/offlinesec_client/req_patch_day.py +64 -0
- offlinesec_client-1.1.0/offlinesec_client/req_sec_notes.py +63 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/rsparam.py +3 -2
- offlinesec_client-1.1.0/offlinesec_client/sap_system.py +69 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client.egg-info/PKG-INFO +4 -1
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client.egg-info/SOURCES.txt +7 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client.egg-info/entry_points.txt +2 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client.egg-info/requires.txt +3 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/setup.py +2 -0
- offlinesec_client-1.0.30/offlinesec_client/__init__.py +0 -1
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/__main__.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/agr_1251.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/config.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/const.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/cwbntcust.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/get_reports.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/req_bo_notes.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/req_notes_report.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/req_param_report.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/req_roles_report.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/resolve_report.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client/sap_gui.py +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client.egg-info/dependency_links.txt +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client.egg-info/top_level.txt +0 -0
- {offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: offlinesec_client
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Offline Security Client
|
|
5
5
|
Home-page: https://offlinesec.com
|
|
6
6
|
Author: Offline Security
|
|
@@ -88,6 +88,9 @@ offlinesec_get_reports
|
|
|
88
88
|
* Our knowledge base is constantly updated and contain all SAP security notes released in 2015-2023. You can find the date of last loaded SAP Security Note in your report.
|
|
89
89
|
* since version 1.0.29 SAP Business Object systems are supported
|
|
90
90
|
* since version 1.0.30 SAP JAVA systems are supported
|
|
91
|
+
* since version 1.1.0 Offlinsec tool supports multi-system scan
|
|
92
|
+
* since version 1.1.0 Offlinesec tool supports last patch day scan
|
|
93
|
+
* since version 1.1.2 the API to integrate with SIEM or VM is available in Offlinesec tool
|
|
91
94
|
|
|
92
95
|
2. Profile Parameters/Compliance Analysis (SAP Security Baseline Checks)
|
|
93
96
|
(Available since version 1.0.12)
|
|
@@ -77,6 +77,9 @@ offlinesec_get_reports
|
|
|
77
77
|
* Our knowledge base is constantly updated and contain all SAP security notes released in 2015-2023. You can find the date of last loaded SAP Security Note in your report.
|
|
78
78
|
* since version 1.0.29 SAP Business Object systems are supported
|
|
79
79
|
* since version 1.0.30 SAP JAVA systems are supported
|
|
80
|
+
* since version 1.1.0 Offlinsec tool supports multi-system scan
|
|
81
|
+
* since version 1.1.0 Offlinesec tool supports last patch day scan
|
|
82
|
+
* since version 1.1.2 the API to integrate with SIEM or VM is available in Offlinesec tool
|
|
80
83
|
|
|
81
84
|
2. Profile Parameters/Compliance Analysis (SAP Security Baseline Checks)
|
|
82
85
|
(Available since version 1.0.12)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.1.0"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from offlinesec_client.sap_system import SAPSystem
|
|
2
|
+
from offlinesec_client.cwbntcust import Cwbntcust
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
ABAP = "ABAP"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ABAPSystem (SAPSystem):
|
|
10
|
+
def __init__(self, **args):
|
|
11
|
+
super().__init__(args["name"])
|
|
12
|
+
self.type = ABAP
|
|
13
|
+
root_dir = args["root_dir"] if "root_dir" in args else None
|
|
14
|
+
|
|
15
|
+
self.kernel_version = ABAPSystem.check_kernel_version(args["krnl_version"]) if "krnl_version" in args.keys() \
|
|
16
|
+
else ""
|
|
17
|
+
self.kernel_patch = ABAPSystem.check_kernel_patch(args["krnl_patch"]) if "krnl_patch" in args.keys() else ""
|
|
18
|
+
self.softs = ABAPSystem.parse_softs_file(args["softs"], root_dir) if "softs" in args.keys() else list()
|
|
19
|
+
if self.kernel_version == "" or len(self.softs) == 0:
|
|
20
|
+
raise ValueError("[ERROR] System '{}' you must specify 'soft' or 'krnl_version' keys"
|
|
21
|
+
.format(self.system_name))
|
|
22
|
+
self.cwbntcust = ABAPSystem.parse_cwbntcust_file(args["cwbntcust"], root_dir, self.system_name) \
|
|
23
|
+
if "cwbntcust" in args.keys() else list()
|
|
24
|
+
self.exclude = ABAPSystem.parse_exclude_file(args["exclude"], root_dir, self.system_name) \
|
|
25
|
+
if "exclude" in args.keys() else list()
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def check_kernel_version(kernel_version):
|
|
29
|
+
kernel_version = str(kernel_version).replace(',', '')
|
|
30
|
+
kernel_version = kernel_version.replace('.', '')
|
|
31
|
+
try:
|
|
32
|
+
return int(kernel_version)
|
|
33
|
+
except ValueError:
|
|
34
|
+
raise ValueError("Kernel Version must be numeric. For example: 7.53 or 753.")
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def check_kernel_patch(kernel_patch):
|
|
38
|
+
kernel_patch = str(kernel_patch).replace(',', '')
|
|
39
|
+
kernel_patch = kernel_patch.replace('.', '')
|
|
40
|
+
try:
|
|
41
|
+
return int(kernel_patch)
|
|
42
|
+
except ValueError:
|
|
43
|
+
raise ValueError("Kernel Version must be numeric. For example: 1100.")
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def parse_softs_file(softs_file, root_dir):
|
|
47
|
+
if root_dir:
|
|
48
|
+
path = os.path.join(root_dir, softs_file)
|
|
49
|
+
else:
|
|
50
|
+
path = softs_file
|
|
51
|
+
if not os.path.exists(path):
|
|
52
|
+
raise FileNotFoundError("File %s not found" % (path,))
|
|
53
|
+
|
|
54
|
+
if not softs_file.upper().endswith(".TXT"):
|
|
55
|
+
raise ValueError("File {} has wrong extension. Only TXT files supported".format(softs_file))
|
|
56
|
+
|
|
57
|
+
softs = list()
|
|
58
|
+
with open(path, 'r', encoding="utf-8") as f:
|
|
59
|
+
num = 0
|
|
60
|
+
for line in f:
|
|
61
|
+
num += 1
|
|
62
|
+
line = line.strip('\r\n').strip()
|
|
63
|
+
line = re.sub(' +', ' ', line).split()
|
|
64
|
+
if len(line) > 4:
|
|
65
|
+
soft = line[0]
|
|
66
|
+
soft = soft.encode("utf-8").decode("utf-8-sig")
|
|
67
|
+
# print(soft.decode('utf-8'))
|
|
68
|
+
version = line[1]
|
|
69
|
+
pkg_num = line[2].lstrip("0")
|
|
70
|
+
if pkg_num == "":
|
|
71
|
+
pkg_num = "0"
|
|
72
|
+
package = line[3]
|
|
73
|
+
softs.append((soft, version, pkg_num, package))
|
|
74
|
+
else:
|
|
75
|
+
print("[ERROR] File %s Line %s: %s" % (softs_file, str(num), " ".join(line)))
|
|
76
|
+
|
|
77
|
+
if not len(softs):
|
|
78
|
+
raise ValueError("File {} has wrong format".format(softs_file))
|
|
79
|
+
return softs
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def parse_cwbntcust_file(cwbntcust_file, root_dir, system_name):
|
|
83
|
+
if root_dir:
|
|
84
|
+
path = os.path.join(root_dir, cwbntcust_file)
|
|
85
|
+
else:
|
|
86
|
+
path = cwbntcust_file
|
|
87
|
+
if not os.path.exists(path):
|
|
88
|
+
raise FileNotFoundError("File %s not found" % (cwbntcust_file,))
|
|
89
|
+
|
|
90
|
+
if not (cwbntcust_file.upper().endswith(".TXT") or cwbntcust_file.upper().endswith(".CSV")):
|
|
91
|
+
raise ValueError("File {} has wrong extension. Only TXT or CSV files supported".format(cwbntcust_file))
|
|
92
|
+
|
|
93
|
+
tbl = Cwbntcust(path)
|
|
94
|
+
notes = tbl.read_file()
|
|
95
|
+
|
|
96
|
+
if not len(notes):
|
|
97
|
+
print("[WARNING] System '{}' File {} has wrong format or doesn't contain completely implemented notes"
|
|
98
|
+
.format(system_name, cwbntcust_file,))
|
|
99
|
+
|
|
100
|
+
return notes
|
|
101
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from offlinesec_client.sap_system import SimpleSystem
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
BO = "BO"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BOSystem (SimpleSystem):
|
|
8
|
+
def __init__(self, **args):
|
|
9
|
+
root_dir = args["root_dir"] if "root_dir" in args else None
|
|
10
|
+
if "version" in args.keys():
|
|
11
|
+
super().__init__(args["name"], version=BOSystem.check_version(args["version"], args["name"]))
|
|
12
|
+
else:
|
|
13
|
+
raise ValueError("You must specify 'version' key")
|
|
14
|
+
self.type = BO
|
|
15
|
+
self.exclude = BOSystem.parse_exclude_file(args["exclude"], root_dir, self.system_name) \
|
|
16
|
+
if "exclude" in args.keys() else list()
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def check_version(v, system_name):
|
|
20
|
+
splitted_ver = str(v).strip('"').split(".")
|
|
21
|
+
if not len(splitted_ver) == 4:
|
|
22
|
+
raise ValueError("Bad SAP BO version format '{}'. Expected format: 14.1.4.1655".format(str(v)))
|
|
23
|
+
|
|
24
|
+
if not int(splitted_ver[0]) == 14:
|
|
25
|
+
raise ValueError("Bad SAP BO version format '{}'. Expected format: 14.1.4.1655".format(str(v)))
|
|
26
|
+
try:
|
|
27
|
+
for item in splitted_ver:
|
|
28
|
+
s = int(item)
|
|
29
|
+
except:
|
|
30
|
+
raise ValueError(" Bad SAP BO version format '{}'. Expected format: 14.1.4.1655".format(str(v)))
|
|
31
|
+
return v
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from .config import config
|
|
2
2
|
import socket
|
|
3
3
|
import argparse
|
|
4
|
+
import json
|
|
4
5
|
import os
|
|
6
|
+
import requests
|
|
7
|
+
from offlinesec_client.const import ERR_MESSAGE
|
|
5
8
|
from offlinesec_client.cwbntcust import Cwbntcust
|
|
6
9
|
from offlinesec_client.const import APIKEY, CLIENT_ID, INST_DATE, ACTION, SYSTEM_NAME, CONNECTION_STR, CWBNTCUST,\
|
|
7
10
|
KRNL_PL, KRNL_VER, VAR
|
|
@@ -109,4 +112,30 @@ def check_num_param(s, title="Argument"):
|
|
|
109
112
|
num = int(s)
|
|
110
113
|
except:
|
|
111
114
|
raise argparse.ArgumentTypeError("%s must be numeric" % (title,))
|
|
112
|
-
return num
|
|
115
|
+
return num
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def send_to_server(data, url, extras={}):
|
|
119
|
+
url = get_connection_str(url)
|
|
120
|
+
|
|
121
|
+
send_data = get_base_json()
|
|
122
|
+
send_data["systems"] = [item.to_dict() for item in data]
|
|
123
|
+
if len(extras):
|
|
124
|
+
send_data.update(extras)
|
|
125
|
+
|
|
126
|
+
files = {'json': ('description', json.dumps(send_data), 'application/json')}
|
|
127
|
+
|
|
128
|
+
r = requests.post(url, files=files)
|
|
129
|
+
if r.content:
|
|
130
|
+
try:
|
|
131
|
+
response = json.loads(r.content)
|
|
132
|
+
if ERR_MESSAGE in response:
|
|
133
|
+
if response[ERR_MESSAGE].startswith("The data successfully"):
|
|
134
|
+
print(" * " + response[ERR_MESSAGE])
|
|
135
|
+
else:
|
|
136
|
+
print("[ERROR] " + response[ERR_MESSAGE])
|
|
137
|
+
return
|
|
138
|
+
except:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
print("[ERROR] No response from the Offline Security server. Please try later")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from offlinesec_client.sap_system import SAPSystem
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
JAVA = "JAVA"
|
|
5
|
+
CSV_COLUMNS = ["Version", "Vendor", "Name", "Location"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JAVASystem (SAPSystem):
|
|
9
|
+
def __init__(self, **args):
|
|
10
|
+
super().__init__(args["name"])
|
|
11
|
+
self.type = JAVA
|
|
12
|
+
root_dir = args["root_dir"] if "root_dir" in args else None
|
|
13
|
+
|
|
14
|
+
self.softs = JAVASystem.parse_softs_file(args["softs"], root_dir) if "softs" in args.keys() else list()
|
|
15
|
+
if len(self.softs) == 0:
|
|
16
|
+
raise ValueError("[ERROR] System '{}' you must specify 'soft' key"
|
|
17
|
+
.format(self.system_name))
|
|
18
|
+
self.exclude = JAVASystem.parse_exclude_file(args["exclude"], root_dir, self.system_name) \
|
|
19
|
+
if "exclude" in args.keys() else list()
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def parse_softs_file(softs_file, root_dir):
|
|
23
|
+
if root_dir:
|
|
24
|
+
path = os.path.join(root_dir, softs_file)
|
|
25
|
+
else:
|
|
26
|
+
path = softs_file
|
|
27
|
+
if not os.path.exists(path):
|
|
28
|
+
raise FileNotFoundError("File %s not found" % (path,))
|
|
29
|
+
if not (softs_file.upper().endswith(".TXT") or softs_file.upper().endswith(".CSV")):
|
|
30
|
+
raise ValueError("File {} has wrong extension. Only TXT or CSV files supported".format(softs_file))
|
|
31
|
+
|
|
32
|
+
out_softs = list()
|
|
33
|
+
|
|
34
|
+
with open(path, 'r') as f:
|
|
35
|
+
columns = dict()
|
|
36
|
+
first_line = True
|
|
37
|
+
for line in f:
|
|
38
|
+
line = line.strip('\r\n')
|
|
39
|
+
if len(line) == 0:
|
|
40
|
+
continue
|
|
41
|
+
line = line.split(',')
|
|
42
|
+
if first_line:
|
|
43
|
+
first_line = False
|
|
44
|
+
for column1 in CSV_COLUMNS:
|
|
45
|
+
for pos, column2 in enumerate(line):
|
|
46
|
+
if column1.lower() == column2.lower().strip():
|
|
47
|
+
columns[column1] = pos
|
|
48
|
+
else:
|
|
49
|
+
name = line[columns[CSV_COLUMNS[2]]]
|
|
50
|
+
ver = line[columns[CSV_COLUMNS[0]]]
|
|
51
|
+
out_softs.append((name, ver))
|
|
52
|
+
|
|
53
|
+
if not len(out_softs):
|
|
54
|
+
raise ValueError("File {} has wrong format".format(softs_file))
|
|
55
|
+
return out_softs
|
|
56
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
from offlinesec_client.const import FILE
|
|
3
|
+
from offlinesec_client.abap_system import ABAPSystem
|
|
4
|
+
from offlinesec_client.java_system import JAVASystem
|
|
5
|
+
from offlinesec_client.bo_system import BOSystem
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def process_yaml_file(args):
|
|
9
|
+
file_name =args[FILE]
|
|
10
|
+
error_list = list()
|
|
11
|
+
system_list = list()
|
|
12
|
+
|
|
13
|
+
with open(file_name, 'r', encoding="utf-8") as f:
|
|
14
|
+
file_content = yaml.safe_load(f)
|
|
15
|
+
|
|
16
|
+
root_dir = file_content["root_dir"] if "root_dir" in file_content else ""
|
|
17
|
+
if not "sap_systems" in file_content:
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
for num, system in enumerate(file_content["sap_systems"]):
|
|
21
|
+
if "name" not in system.keys():
|
|
22
|
+
system["name"] = "System {}".format(str(num+1))
|
|
23
|
+
system_type = system["type"] if "type" in system.keys() else ""
|
|
24
|
+
if root_dir != "":
|
|
25
|
+
system["root_dir"] = root_dir
|
|
26
|
+
if system_type == "":
|
|
27
|
+
err = "[ERROR] System '{}' doesn't contain key 'type'".format(system["name"])
|
|
28
|
+
print(err)
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
if system_type.upper().strip() == "ABAP":
|
|
33
|
+
new_item = ABAPSystem(**system)
|
|
34
|
+
elif system_type.upper().strip() == "JAVA":
|
|
35
|
+
new_item = JAVASystem(**system)
|
|
36
|
+
elif system_type.upper().strip() == "BO":
|
|
37
|
+
new_item = BOSystem(**system)
|
|
38
|
+
else:
|
|
39
|
+
err = "[ERROR] System '{}' Unknown system type '{}'".format(system["name"], system_type)
|
|
40
|
+
print(err)
|
|
41
|
+
continue
|
|
42
|
+
except (FileNotFoundError, ValueError) as error:
|
|
43
|
+
err = "[ERROR] System '{}' {}".format(system["name"], str(error))
|
|
44
|
+
print(err)
|
|
45
|
+
continue
|
|
46
|
+
else:
|
|
47
|
+
if new_item:
|
|
48
|
+
system_list.append(new_item)
|
|
49
|
+
|
|
50
|
+
return system_list
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import yaml
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
import os
|
|
6
|
+
import offlinesec_client.func
|
|
7
|
+
from offlinesec_client.const import FILE
|
|
8
|
+
from offlinesec_client.multi_systems import process_yaml_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# SUPPORTED_SYSTEMS = ["ABAP", "JAVA", "BO", "HANA"]
|
|
12
|
+
UPLOAD_URL = "/sec-notes"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def check_file_arg(s):
|
|
16
|
+
res = offlinesec_client.func.check_file_arg(s, ['yaml'], 200000)
|
|
17
|
+
with open(s, 'r') as file:
|
|
18
|
+
yaml_content = yaml.safe_load(file)
|
|
19
|
+
if "sap_systems" not in yaml_content:
|
|
20
|
+
raise argparse.ArgumentTypeError("Wrong the YAML file (%s) structure. The 'sap_systems' key not found" % (s,))
|
|
21
|
+
return res
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def init_args():
|
|
25
|
+
parser = argparse.ArgumentParser()
|
|
26
|
+
parser.add_argument("-f", "--file", action="store", type=check_file_arg,
|
|
27
|
+
help="File Name (SAP systems (ABAP, JAVA, BO, ...) and their software components in YAML format)", required=True)
|
|
28
|
+
parser.add_argument('--wait', action='store_true', help="Wait 5 minutes and download the report")
|
|
29
|
+
parser.parse_args()
|
|
30
|
+
return vars(parser.parse_args())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def print_errors(errors):
|
|
34
|
+
for error in errors:
|
|
35
|
+
print(error)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def process_it(args):
|
|
39
|
+
systems = process_yaml_file(args)
|
|
40
|
+
additional_keys = dict()
|
|
41
|
+
additional_keys["patch_day"] = True
|
|
42
|
+
offlinesec_client.func.send_to_server(systems, UPLOAD_URL, additional_keys)
|
|
43
|
+
|
|
44
|
+
wait = args["wait"]
|
|
45
|
+
if wait:
|
|
46
|
+
for remaining in range(310, 0, -1):
|
|
47
|
+
sys.stdout.write("\r")
|
|
48
|
+
sys.stdout.write("{:2d} seconds remaining.".format(remaining))
|
|
49
|
+
sys.stdout.flush()
|
|
50
|
+
time.sleep(1)
|
|
51
|
+
os.system("offlinesec_get_reports")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main():
|
|
55
|
+
args = init_args()
|
|
56
|
+
if FILE in args and args[FILE]:
|
|
57
|
+
process_it(args)
|
|
58
|
+
else:
|
|
59
|
+
print("Please choose the configuration YAML file (-f option)")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
main()
|
|
64
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import yaml
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
import os
|
|
6
|
+
import offlinesec_client.func
|
|
7
|
+
from offlinesec_client.const import FILE
|
|
8
|
+
from offlinesec_client.multi_systems import process_yaml_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# SUPPORTED_SYSTEMS = ["ABAP", "JAVA", "BO", "HANA"]
|
|
12
|
+
UPLOAD_URL = "/sec-notes"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def check_file_arg(s):
|
|
16
|
+
res = offlinesec_client.func.check_file_arg(s, ['yaml'], 200000)
|
|
17
|
+
with open(s, 'r') as file:
|
|
18
|
+
yaml_content = yaml.safe_load(file)
|
|
19
|
+
if "sap_systems" not in yaml_content:
|
|
20
|
+
raise argparse.ArgumentTypeError("Wrong the YAML file (%s) structure. The 'sap_systems' key not found" % (s,))
|
|
21
|
+
return res
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def init_args():
|
|
25
|
+
parser = argparse.ArgumentParser()
|
|
26
|
+
parser.add_argument("-f", "--file", action="store", type=check_file_arg,
|
|
27
|
+
help="File Name (SAP systems (ABAP, JAVA, BO, ...) and their software components in YAML format)", required=True)
|
|
28
|
+
parser.add_argument('--wait', action='store_true', help="Wait 5 minutes and download the report")
|
|
29
|
+
parser.parse_args()
|
|
30
|
+
return vars(parser.parse_args())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def print_errors(errors):
|
|
34
|
+
for error in errors:
|
|
35
|
+
print(error)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def process_it(args):
|
|
39
|
+
systems = process_yaml_file(args)
|
|
40
|
+
additional_keys = dict()
|
|
41
|
+
offlinesec_client.func.send_to_server(systems, UPLOAD_URL, additional_keys)
|
|
42
|
+
|
|
43
|
+
wait = args["wait"]
|
|
44
|
+
if wait:
|
|
45
|
+
for remaining in range(310, 0, -1):
|
|
46
|
+
sys.stdout.write("\r")
|
|
47
|
+
sys.stdout.write("{:2d} seconds remaining.".format(remaining))
|
|
48
|
+
sys.stdout.flush()
|
|
49
|
+
time.sleep(1)
|
|
50
|
+
os.system("offlinesec_get_reports")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main():
|
|
54
|
+
args = init_args()
|
|
55
|
+
if FILE in args and args[FILE]:
|
|
56
|
+
process_it(args)
|
|
57
|
+
else:
|
|
58
|
+
print("Please choose the configuration YAML file (-f option)")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
main()
|
|
63
|
+
|
|
@@ -8,7 +8,7 @@ from offlinesec_client.const import PARAM_NAME, PARAM_DESC, PARAM_VALUE
|
|
|
8
8
|
|
|
9
9
|
HIDE_PARAMS = ["FN_BDCALTLOG", "FN_BDCLOG", "FN_EXTRACT", "SAPARGV", "SAPGLOBALHOST", "SAPLOCALHOST",
|
|
10
10
|
"SAPLOCALHOSTFULL", "SAPPROFILE", "SAPPROFILE_IN_EFFECT", "SAPSYSTEMNAME", "SETENV_.*", "Execute_.*",
|
|
11
|
-
"Start_Program_.*", "_\S{2}", "dbs/hdb/schema", "dbs/mss/dbname", "dbs/ora/tnsname", "dbs/syb/dbname",
|
|
11
|
+
"Start_Program_.*", r"_\S{2}", "dbs/hdb/schema", "dbs/mss/dbname", "dbs/ora/tnsname", "dbs/syb/dbname",
|
|
12
12
|
"enq/server/schema_0", "enque/encni/hostname", "enque/serverhost", "igs/listener/rfc", "igs/mux/ip",
|
|
13
13
|
"ms/comment", "rdisp/j2ee_profile", "rdisp/j2ee_profile", "rdisp/mshost", "rdisp/msserv", "rdisp/myname",
|
|
14
14
|
"rlfw/bri/msserv", "rlfw/upg/msserv", "snc/gssapi_lib", "snc/identity/as", "vmcj/debug_proxy/cfg/msHost",
|
|
@@ -55,7 +55,8 @@ class RsparamReport:
|
|
|
55
55
|
def mask(param_name, param_value):
|
|
56
56
|
DEFAULT_VALUE = "XXX"
|
|
57
57
|
for hide_param in HIDE_PARAMS:
|
|
58
|
-
|
|
58
|
+
re_str = "(?i)^" + hide_param.lower() + "$"
|
|
59
|
+
res = re.match(re_str, param_name)
|
|
59
60
|
if res:
|
|
60
61
|
return DEFAULT_VALUE if param_value != "" else ""
|
|
61
62
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
import yaml
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SAPSystem:
|
|
7
|
+
def __init__(self, system_name=""):
|
|
8
|
+
self.system_name = system_name
|
|
9
|
+
|
|
10
|
+
def to_dict(self):
|
|
11
|
+
return self.__dict__
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def parse_exclude_file(exclude_yaml_file, root_dir, system_name):
|
|
15
|
+
if root_dir:
|
|
16
|
+
path = os.path.join(root_dir, exclude_yaml_file)
|
|
17
|
+
else:
|
|
18
|
+
path = exclude_yaml_file
|
|
19
|
+
if not os.path.exists(path):
|
|
20
|
+
raise FileNotFoundError("File %s not found" % (path,))
|
|
21
|
+
|
|
22
|
+
if not exclude_yaml_file.upper().endswith(".YAML"):
|
|
23
|
+
raise ValueError("File {} has wrong extension. Only YAML file supported".format(exclude_yaml_file))
|
|
24
|
+
try:
|
|
25
|
+
with open(path, 'r', encoding="utf-8") as f:
|
|
26
|
+
file_content = yaml.safe_load(f)
|
|
27
|
+
except:
|
|
28
|
+
raise ValueError("File {} has wrong structure. Only YAML files supported".format(path))
|
|
29
|
+
else:
|
|
30
|
+
outlist = list()
|
|
31
|
+
note_num = 0
|
|
32
|
+
for item in file_content:
|
|
33
|
+
note_num += 1
|
|
34
|
+
if "note" not in item.keys():
|
|
35
|
+
print("[WARNING] System {} File {} note {} has no key 'note'".format(system_name,
|
|
36
|
+
exclude_yaml_file,
|
|
37
|
+
str(note_num)))
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
note_id = int(item["note"])
|
|
42
|
+
|
|
43
|
+
except ValueError as err:
|
|
44
|
+
print("[WARNING] System {} File {} contains wrong Note ID {}".format(system_name,
|
|
45
|
+
exclude_yaml_file,
|
|
46
|
+
item["note"]))
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
until_date = item["until"] if "until" in item.keys() else ""
|
|
50
|
+
comment = item["comment"] if "comment" in item.keys() else ""
|
|
51
|
+
if until_date:
|
|
52
|
+
try:
|
|
53
|
+
until = datetime.strptime(until_date, "%m.%d.%Y")
|
|
54
|
+
except ValueError as err:
|
|
55
|
+
print("[WARNING] System {} File {} contains wrong Date {}".format(system_name,
|
|
56
|
+
exclude_yaml_file,
|
|
57
|
+
item["until"]))
|
|
58
|
+
until_date = ""
|
|
59
|
+
|
|
60
|
+
outlist.append((note_id, until_date, comment))
|
|
61
|
+
|
|
62
|
+
return outlist
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SimpleSystem(SAPSystem):
|
|
66
|
+
def __init__(self, system_name="", version=""):
|
|
67
|
+
super().__init__(system_name)
|
|
68
|
+
self.version = version
|
|
69
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: offlinesec-client
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Offline Security Client
|
|
5
5
|
Home-page: https://offlinesec.com
|
|
6
6
|
Author: Offline Security
|
|
@@ -88,6 +88,9 @@ offlinesec_get_reports
|
|
|
88
88
|
* Our knowledge base is constantly updated and contain all SAP security notes released in 2015-2023. You can find the date of last loaded SAP Security Note in your report.
|
|
89
89
|
* since version 1.0.29 SAP Business Object systems are supported
|
|
90
90
|
* since version 1.0.30 SAP JAVA systems are supported
|
|
91
|
+
* since version 1.1.0 Offlinsec tool supports multi-system scan
|
|
92
|
+
* since version 1.1.0 Offlinesec tool supports last patch day scan
|
|
93
|
+
* since version 1.1.2 the API to integrate with SIEM or VM is available in Offlinesec tool
|
|
91
94
|
|
|
92
95
|
2. Profile Parameters/Compliance Analysis (SAP Security Baseline Checks)
|
|
93
96
|
(Available since version 1.0.12)
|
|
@@ -2,20 +2,27 @@ README.md
|
|
|
2
2
|
setup.py
|
|
3
3
|
offlinesec_client/__init__.py
|
|
4
4
|
offlinesec_client/__main__.py
|
|
5
|
+
offlinesec_client/abap_system.py
|
|
5
6
|
offlinesec_client/agr_1251.py
|
|
7
|
+
offlinesec_client/bo_system.py
|
|
6
8
|
offlinesec_client/config.py
|
|
7
9
|
offlinesec_client/const.py
|
|
8
10
|
offlinesec_client/cwbntcust.py
|
|
9
11
|
offlinesec_client/func.py
|
|
10
12
|
offlinesec_client/get_reports.py
|
|
13
|
+
offlinesec_client/java_system.py
|
|
14
|
+
offlinesec_client/multi_systems.py
|
|
11
15
|
offlinesec_client/req_bo_notes.py
|
|
12
16
|
offlinesec_client/req_java_notes.py
|
|
13
17
|
offlinesec_client/req_notes_report.py
|
|
14
18
|
offlinesec_client/req_param_report.py
|
|
19
|
+
offlinesec_client/req_patch_day.py
|
|
15
20
|
offlinesec_client/req_roles_report.py
|
|
21
|
+
offlinesec_client/req_sec_notes.py
|
|
16
22
|
offlinesec_client/resolve_report.py
|
|
17
23
|
offlinesec_client/rsparam.py
|
|
18
24
|
offlinesec_client/sap_gui.py
|
|
25
|
+
offlinesec_client/sap_system.py
|
|
19
26
|
offlinesec_client.egg-info/PKG-INFO
|
|
20
27
|
offlinesec_client.egg-info/SOURCES.txt
|
|
21
28
|
offlinesec_client.egg-info/dependency_links.txt
|
{offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client.egg-info/entry_points.txt
RENAMED
|
@@ -3,7 +3,9 @@ offlinesec_bo_notes = offlinesec_client.req_bo_notes:main
|
|
|
3
3
|
offlinesec_get_reports = offlinesec_client.get_reports:main
|
|
4
4
|
offlinesec_inverse_transform = offlinesec_client.resolve_report:main
|
|
5
5
|
offlinesec_java_notes = offlinesec_client.req_java_notes:main
|
|
6
|
+
offlinesec_patch_day = offlinesec_client.req_patch_day:main
|
|
6
7
|
offlinesec_sap_notes = offlinesec_client.req_notes_report:main
|
|
7
8
|
offlinesec_sap_params = offlinesec_client.req_param_report:main
|
|
8
9
|
offlinesec_sap_roles = offlinesec_client.req_roles_report:main
|
|
10
|
+
offlinesec_sec_notes = offlinesec_client.req_sec_notes:main
|
|
9
11
|
|
|
@@ -18,6 +18,8 @@ setup(
|
|
|
18
18
|
long_description_content_type="text/markdown",
|
|
19
19
|
long_description=long_description,
|
|
20
20
|
entry_points={'console_scripts': ['offlinesec_sap_notes = offlinesec_client.req_notes_report:main',
|
|
21
|
+
'offlinesec_sec_notes = offlinesec_client.req_sec_notes:main',
|
|
22
|
+
'offlinesec_patch_day = offlinesec_client.req_patch_day:main',
|
|
21
23
|
'offlinesec_get_reports = offlinesec_client.get_reports:main',
|
|
22
24
|
'offlinesec_sap_params = offlinesec_client.req_param_report:main',
|
|
23
25
|
'offlinesec_sap_roles = offlinesec_client.req_roles_report:main',
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.0.30"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{offlinesec_client-1.0.30 → offlinesec_client-1.1.0}/offlinesec_client.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|