Skip to content

Commit 626f3cf

Browse files
authored
Merge branch 'main' into move/675-node-to-node-encryption
2 parents ce8668a + ef46e2d commit 626f3cf

File tree

9 files changed

+342
-289
lines changed

9 files changed

+342
-289
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
**/__pycache__/
22
.venv/
33
.idea
4+
.sandbox
45
.DS_Store
56
node_modules
67
Tests/kaas/results/

Standards/scs-0102-v1-image-metadata.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: SCS Image Metadata Standard
2+
title: SCS Image Metadata
33
type: Standard
44
stabilized_at: 2022-10-31
55
status: Stable

Standards/scs-0118-v1-taxonomy-of-failsafe-levels.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Taxonomy of Failsafe Levels
2+
title: SCS Taxonomy of Failsafe Levels
33
type: Decision Record
44
status: Draft
55
track: IaaS

Tests/iaas/key-manager/check-for-key-manager.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,16 @@ def main():
127127
# parse cloud name for lookup in clouds.yaml
128128
cloud = args.os_cloud or os.environ.get("OS_CLOUD", None)
129129
if not cloud:
130-
raise RuntimeError(
130+
logger.critical(
131131
"You need to have the OS_CLOUD environment variable set to your cloud "
132132
"name or pass it via --os-cloud"
133133
)
134+
return 2
134135

135136
with openstack.connect(cloud=cloud) as conn:
136137
if not check_for_member_role(conn):
137138
logger.critical("Cannot test key-manager permissions. User has wrong roles")
138-
return 1
139+
return 2
139140
if check_presence_of_key_manager(conn):
140141
return check_key_manager_permissions(conn)
141142
else:
@@ -145,9 +146,11 @@ def main():
145146

146147
if __name__ == "__main__":
147148
try:
148-
sys.exit(main())
149-
except SystemExit:
149+
sys.exit(main() or 0)
150+
except SystemExit as e:
151+
if e.code < 2:
152+
print("key-manager-check: " + ('PASS', 'FAIL')[min(1, e.code)])
150153
raise
151154
except BaseException:
152155
logger.critical("exception", exc_info=True)
153-
sys.exit(1)
156+
sys.exit(2)
Lines changed: 146 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,181 @@
1+
#!/usr/bin/env python3
12
"""Default Security Group Rules Checker
23
34
This script tests the absence of any ingress default security group rule
45
except for ingress rules from the same Security Group. Furthermore the
56
presence of default rules for egress traffic is checked.
67
"""
8+
import argparse
9+
from collections import Counter
10+
import logging
11+
import os
12+
import sys
713

814
import openstack
9-
import os
10-
import argparse
15+
from openstack.exceptions import ResourceNotFound
1116

17+
logger = logging.getLogger(__name__)
1218

13-
def connect(cloud_name: str) -> openstack.connection.Connection:
14-
"""Create a connection to an OpenStack cloud
19+
SG_NAME = "scs-test-default-sg"
20+
DESCRIPTION = "scs-test-default-sg"
1521

16-
:param string cloud_name:
17-
The name of the configuration to load from clouds.yaml.
1822

19-
:returns: openstack.connnection.Connection
23+
def check_default_rules(rules, short=False):
2024
"""
21-
return openstack.connect(
22-
cloud=cloud_name,
23-
)
25+
counts all verall ingress rules and egress rules, depending on the requested testing mode
2426
25-
26-
def test_rules(cloud_name: str):
27-
try:
28-
connection = connect(cloud_name)
29-
rules = connection.network.default_security_group_rules()
30-
except Exception as e:
31-
print(str(e))
32-
raise Exception(
33-
f"Connection to cloud '{cloud_name}' was not successfully. "
34-
f"The default Security Group Rules could not be accessed. "
35-
f"Please check your cloud connection and authorization."
36-
)
37-
38-
# count all overall ingress rules and egress rules.
39-
ingress_rules = 0
40-
ingress_from_same_sg = 0
41-
egress_rules = 0
42-
egress_ipv4_default_sg = 0
43-
egress_ipv4_custom_sg = 0
44-
egress_ipv6_default_sg = 0
45-
egress_ipv6_custom_sg = 0
27+
:param bool short
28+
if short is True, the testing mode is set on short for older OpenStack versions
29+
"""
30+
ingress_rules = egress_rules = 0
31+
egress_vars = {'IPv4': {}, 'IPv6': {}}
32+
for key, value in egress_vars.items():
33+
value['default'] = 0
34+
if not short:
35+
value['custom'] = 0
4636
if not rules:
47-
print("No default security group rules defined.")
48-
else:
49-
for rule in rules:
50-
direction = rule.direction
51-
ethertype = rule.ethertype
52-
r_custom_sg = rule.used_in_non_default_sg
53-
r_default_sg = rule.used_in_default_sg
54-
if direction == "ingress":
55-
ingress_rules += 1
37+
logger.info("No default security group rules defined.")
38+
for rule in rules:
39+
direction = rule["direction"]
40+
ethertype = rule["ethertype"]
41+
if direction == "ingress":
42+
if not short:
5643
# we allow ingress from the same security group
5744
# but only for the default security group
58-
r_group_id = rule.remote_group_id
59-
if (r_group_id == "PARENT" and not r_custom_sg):
60-
ingress_from_same_sg += 1
61-
elif direction == "egress" and ethertype == "IPv4":
62-
egress_rules += 1
63-
if rule.remote_ip_prefix:
64-
# this rule does not allow traffic to all external ips
65-
continue
66-
if r_custom_sg:
67-
egress_ipv4_custom_sg += 1
68-
if r_default_sg:
69-
egress_ipv4_default_sg += 1
70-
elif direction == "egress" and ethertype == "IPv6":
71-
egress_rules += 1
72-
if rule.remote_ip_prefix:
73-
# this rule does not allow traffic to all external ips
45+
if rule.remote_group_id == "PARENT" and not rule["used_in_non_default_sg"]:
7446
continue
75-
if r_custom_sg:
76-
egress_ipv6_custom_sg += 1
77-
if r_default_sg:
78-
egress_ipv6_default_sg += 1
79-
80-
# test whether there are no other than the allowed ingress rules
81-
assert ingress_rules == ingress_from_same_sg, (
82-
f"Expected only ingress rules for default security groups, "
83-
f"that allow ingress traffic from the same group. "
84-
f"But there are more - in total {ingress_rules} ingress rules. "
85-
f"There should be only {ingress_from_same_sg} ingress rules.")
86-
assert egress_rules > 0, (
87-
f"Expected to have more than {egress_rules} egress rules present.")
88-
var_list = [egress_ipv4_default_sg, egress_ipv4_custom_sg,
89-
egress_ipv6_default_sg, egress_ipv6_custom_sg]
90-
assert all([var > 0 for var in var_list]), (
91-
"Not all expected egress rules are present. "
92-
"Expected rules for egress for IPv4 and IPv6 "
93-
"both for default and custom security groups.")
94-
95-
result_dict = {
96-
"Ingress Rules": ingress_rules,
97-
"Egress Rules": egress_rules
98-
}
99-
return result_dict
47+
ingress_rules += 1
48+
elif direction == "egress" and ethertype in egress_vars:
49+
egress_rules += 1
50+
if short:
51+
egress_vars[ethertype]['default'] += 1
52+
continue
53+
if rule.remote_ip_prefix:
54+
# this rule does not allow traffic to all external ips
55+
continue
56+
# note: these two are not mutually exclusive
57+
if rule["used_in_default_sg"]:
58+
egress_vars[ethertype]['default'] += 1
59+
if rule["used_in_non_default_sg"]:
60+
egress_vars[ethertype]['custom'] += 1
61+
# test whether there are no unallowed ingress rules
62+
if ingress_rules:
63+
logger.error(f"Expected no default ingress rules, found {ingress_rules}.")
64+
# test whether all expected egress rules are present
65+
missing = [(key, key2) for key, val in egress_vars.items() for key2, val2 in val.items() if not val2]
66+
if missing:
67+
logger.error(
68+
"Expected rules for egress for IPv4 and IPv6 both for default and custom security groups. "
69+
f"Missing rule types: {', '.join(str(x) for x in missing)}"
70+
)
71+
logger.info(str({
72+
"Unallowed Ingress Rules": ingress_rules,
73+
"Egress Rules": egress_rules,
74+
}))
75+
76+
77+
def create_security_group(conn, sg_name: str = SG_NAME, description: str = DESCRIPTION):
78+
"""Create security group in openstack
79+
80+
:returns:
81+
~openstack.network.v2.security_group.SecurityGroup: The new security group or None
82+
"""
83+
sg = conn.network.create_security_group(name=sg_name, description=description)
84+
return sg.id
85+
86+
87+
def delete_security_group(conn, sg_id):
88+
conn.network.delete_security_group(sg_id)
89+
# in case of a successful delete finding the sg will throw an exception
90+
try:
91+
conn.network.find_security_group(name_or_id=sg_id)
92+
except ResourceNotFound:
93+
logger.debug(f"Security group {sg_id} was deleted successfully.")
94+
except Exception:
95+
logger.critical(f"Security group {sg_id} was not deleted successfully")
96+
raise
97+
98+
99+
def altern_test_rules(connection: openstack.connection.Connection):
100+
sg_id = create_security_group(connection)
101+
try:
102+
sg = connection.network.find_security_group(name_or_id=sg_id)
103+
check_default_rules(sg.security_group_rules, short=True)
104+
finally:
105+
delete_security_group(connection, sg_id)
106+
107+
108+
def test_rules(connection: openstack.connection.Connection):
109+
try:
110+
rules = list(connection.network.default_security_group_rules())
111+
except ResourceNotFound:
112+
logger.info(
113+
"API call failed. OpenStack components might not be up to date. "
114+
"Falling back to old-style test method. "
115+
)
116+
logger.debug("traceback", exc_info=True)
117+
altern_test_rules(connection)
118+
else:
119+
check_default_rules(rules)
120+
121+
122+
class CountingHandler(logging.Handler):
123+
def __init__(self, level=logging.NOTSET):
124+
super().__init__(level=level)
125+
self.bylevel = Counter()
126+
127+
def handle(self, record):
128+
self.bylevel[record.levelno] += 1
100129

101130

102131
def main():
103132
parser = argparse.ArgumentParser(
104-
description="SCS Default Security Group Rules Checker")
133+
description="SCS Default Security Group Rules Checker",
134+
)
105135
parser.add_argument(
106-
"--os-cloud", type=str,
136+
"--os-cloud",
137+
type=str,
107138
help="Name of the cloud from clouds.yaml, alternative "
108-
"to the OS_CLOUD environment variable"
139+
"to the OS_CLOUD environment variable",
109140
)
110141
parser.add_argument(
111-
"--debug", action="store_true",
112-
help="Enable OpenStack SDK debug logging"
142+
"--debug", action="store_true", help="Enable debug logging",
113143
)
114144
args = parser.parse_args()
115145
openstack.enable_logging(debug=args.debug)
146+
logging.basicConfig(
147+
format="%(levelname)s: %(message)s",
148+
level=logging.DEBUG if args.debug else logging.INFO,
149+
)
150+
151+
# count the number of log records per level (used for summary and return code)
152+
counting_handler = CountingHandler(level=logging.INFO)
153+
logger.addHandler(counting_handler)
116154

117155
# parse cloud name for lookup in clouds.yaml
118-
cloud = os.environ.get("OS_CLOUD", None)
119-
if args.os_cloud:
120-
cloud = args.os_cloud
121-
assert cloud, (
122-
"You need to have the OS_CLOUD environment variable set to your cloud "
123-
"name or pass it via --os-cloud"
124-
)
156+
cloud = args.os_cloud or os.environ.get("OS_CLOUD", None)
157+
if not cloud:
158+
raise ValueError(
159+
"You need to have the OS_CLOUD environment variable set to your cloud "
160+
"name or pass it via --os-cloud"
161+
)
125162

126-
print(test_rules(cloud))
163+
with openstack.connect(cloud) as conn:
164+
test_rules(conn)
165+
166+
c = counting_handler.bylevel
167+
logger.debug(f"Total critical / error / warning: {c[logging.CRITICAL]} / {c[logging.ERROR]} / {c[logging.WARNING]}")
168+
if not c[logging.CRITICAL]:
169+
print("security-groups-default-rules-check: " + ('PASS', 'FAIL')[min(1, c[logging.ERROR])])
170+
return min(127, c[logging.CRITICAL] + c[logging.ERROR]) # cap at 127 due to OS restrictions
127171

128172

129173
if __name__ == "__main__":
130-
main()
174+
try:
175+
sys.exit(main())
176+
except SystemExit:
177+
raise
178+
except BaseException as exc:
179+
logging.debug("traceback", exc_info=True)
180+
logging.critical(str(exc))
181+
sys.exit(1)

0 commit comments

Comments
 (0)