Skip to content

Commit ff829e7

Browse files
fraugabelmbuechse
andauthored
Issue/718 check for key manager (#760)
* changed synthesis of auth_url Signed-off-by: Katharina Trentau <[email protected]> * trying to find os sdk method to request user role Signed-off-by: Katharina Trentau <[email protected]> * alternating between auth methods Signed-off-by: Katharina Trentau <[email protected]> * alternating between auth methods when catching an auth error Signed-off-by: Katharina Trentau <[email protected]> * modularized Signed-off-by: Katharina Trentau <[email protected]> * start refracturing Signed-off-by: Katharina Trentau <[email protected]> * added auth url option Signed-off-by: Katharina Trentau <[email protected]> * debugged �[200~ auth_url = synth_auth_url(auth_data['auth_url']) Signed-off-by: Katharina Trentau <[email protected]> * debugged synth_auth_url() Signed-off-by: Katharina Trentau <[email protected]> * formatting Signed-off-by: Katharina Trentau <[email protected]> * refractured Signed-off-by: Katharina Trentau <[email protected]> * fetching token and identity role through keystone lib Signed-off-by: Katharina Trentau <[email protected]> * at authentification error continue with fernet token instead of reconnecting Signed-off-by: Katharina Trentau <[email protected]> * at authentification error continue with fernet token instead of reconnecting Signed-off-by: Katharina Trentau <[email protected]> * at authentification error continue with fernet token instead of reconnecting Signed-off-by: Katharina Trentau <[email protected]> * at authentification error continue with fernet token instead of reconnecting Signed-off-by: Katharina Trentau <[email protected]> * changed everything to session token requests only Signed-off-by: Katharina Trentau <[email protected]> * before refracturing with reconnecting still in it but commented Signed-off-by: Katharina Trentau <[email protected]> * stripped Signed-off-by: Katharina Trentau <[email protected]> * stripped and blacked again for flake8 Signed-off-by: Katharina Trentau <[email protected]> * tested against new devstack Signed-off-by: Katharina Trentau <[email protected]> * tested against new devstack Signed-off-by: Katharina Trentau <[email protected]> * changed description Signed-off-by: Katharina Trentau <[email protected]> * make check script executable Signed-off-by: Matthias Büchse <[email protected]> * Removed a whole lot of unnecessary code Signed-off-by: Matthias Büchse <[email protected]> * initialized logger changed prints to logs Signed-off-by: Katharina Trentau <[email protected]> * changed assert to raised exception Signed-off-by: Katharina Trentau <[email protected]> * sadisfy flake Signed-off-by: Katharina Trentau <[email protected]> * Revised structure, logging, error handling, return code, documentation Signed-off-by: Matthias Büchse <[email protected]> * mention test script in official document Signed-off-by: Matthias Büchse <[email protected]> * Acquiesce flake8 Signed-off-by: Matthias Büchse <[email protected]> --------- Signed-off-by: Katharina Trentau <[email protected]> Signed-off-by: Matthias Büchse <[email protected]> Co-authored-by: Katharina Trentau <[email protected]> Co-authored-by: Matthias Büchse <[email protected]>
1 parent ced5954 commit ff829e7

File tree

2 files changed

+99
-131
lines changed

2 files changed

+99
-131
lines changed

Standards/scs-0116-w1-key-manager-implementation-testing.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ This can be done with a small change in the policy.yaml file. The `creator` has
4444
The check for the presence of a Key Manager is done with a test script, that checks the presence of a Key Manager service in the catalog endpoint of Openstack.
4545
This check can eventually be moved to the checks for the mandatory an supported service/API list, in case of a promotion of the Key Manager to the mandatory list.
4646
47+
### Implementation
48+
49+
The script [`check-for-key-manager.py`](https://github.com/SovereignCloudStack/standards/blob/main/Tests/iaas/key-manager/check-for-key-manager.py)
50+
connects to OpenStack and performs the checks described in this section.
51+
4752
## Manual Tests
4853

4954
It is not possible to check a deployment for a correctly protected Master KEK automatically from the outside.

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

100644100755
Lines changed: 94 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,190 +1,153 @@
1-
"""Mandatory APIs checker
1+
#!/usr/bin/env python3
2+
"""Key Manager service checker for scs-0116-v1-key-manager-standard.md
3+
24
This script retrieves the endpoint catalog from Keystone using the OpenStack
3-
SDK and checks whether a key manager APi endpoint is present.
5+
SDK and checks whether a key manager API endpoint is present.
6+
It then checks whether a user with the maximum of a member role can create secrets.
7+
This will only work after policy adjustments or with the new secure RBAC roles and policies.
48
The script relies on an OpenStack SDK compatible clouds.yaml file for
59
authentication with Keystone.
610
"""
711

812
import argparse
9-
import json
1013
import logging
1114
import os
15+
import sys
1216

1317
import openstack
1418

15-
1619
logger = logging.getLogger(__name__)
1720

1821

19-
def connect(cloud_name: str) -> openstack.connection.Connection:
20-
"""Create a connection to an OpenStack cloud
21-
:param string cloud_name:
22-
The name of the configuration to load from clouds.yaml.
23-
:returns: openstack.connnection.Connection
24-
"""
25-
return openstack.connect(
26-
cloud=cloud_name,
27-
)
22+
def initialize_logging():
23+
logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO)
2824

2925

30-
def check_for_member_role(conn: openstack.connection.Connection
31-
) -> None:
32-
"""Checks whether the current user has at maximum privileges
33-
of the member role.
34-
:param connection:
35-
The current connection to an OpenStack cloud.
36-
:returns: boolean, when role with most priviledges is member
37-
"""
26+
def check_for_member_role(conn: openstack.connection.Connection) -> None:
27+
"""Checks whether the current user has at maximum privileges of the member role.
3828
39-
auth_data = conn.auth
40-
auth_dict = {
41-
"identity": {
42-
"methods": ["password"],
43-
"password": {
44-
"user": {
45-
"name": auth_data['username'],
46-
"domain": {"name": auth_data['project_domain_name']},
47-
"password": auth_data['password']
48-
}
49-
},
50-
},
51-
"scope": {
52-
"project": {
53-
"domain": {"name": auth_data['project_domain_name']},
54-
"name": auth_data['project_name']
55-
}
56-
}
57-
}
58-
59-
has_member_role = False
60-
request = conn.session.request(auth_data['auth_url'] + '/v3/auth/tokens',
61-
'POST',
62-
json={'auth': auth_dict})
63-
for role in json.loads(request.content)["token"]["roles"]:
64-
role_name = role["name"]
65-
if role_name == "admin" or role_name == "manager":
66-
return False
67-
elif role_name == "member":
68-
print("User has member role.")
69-
has_member_role = True
70-
elif role_name == "reader":
71-
print("User has reader role.")
72-
else:
73-
print("User has custom role.")
74-
return False
75-
return has_member_role
76-
77-
78-
def check_presence_of_key_manager(cloud_name: str):
29+
:param conn: connection to an OpenStack cloud.
30+
:returns: boolean, when role with most privileges is member
31+
"""
32+
role_names = set(conn.session.auth.get_access(conn.session).role_names)
33+
if role_names & {"admin", "manager"}:
34+
return False
35+
if "reader" in role_names:
36+
logger.info("User has reader role.")
37+
custom_roles = sorted(role_names - {"reader", "member"})
38+
if custom_roles:
39+
logger.info(f"User has custom roles {', '.join(custom_roles)}.")
40+
return "member" in role_names
41+
42+
43+
def check_presence_of_key_manager(conn: openstack.connection.Connection) -> None:
7944
try:
80-
connection = connect(cloud_name)
81-
services = connection.service_catalog
82-
except Exception as e:
83-
print(str(e))
84-
raise Exception(
85-
f"Connection to cloud '{cloud_name}' was not successfully. "
86-
f"The Catalog endpoint could not be accessed. "
87-
f"Please check your cloud connection and authorization."
88-
)
45+
services = conn.service_catalog
46+
except Exception:
47+
logger.critical("Could not access Catalog endpoint.")
48+
raise
8949

9050
for svc in services:
91-
svc_type = svc['type']
51+
svc_type = svc["type"]
9252
if svc_type == "key-manager":
9353
# key-manager is present
9454
# now we want to check whether a user with member role
9555
# can create and access secrets
96-
check_key_manager_permissions(connection)
97-
return 0
56+
logger.info("Key Manager is present")
57+
return True
9858

99-
# we did not find the key-manager service
100-
logger.warning("There is no key-manager endpoint in the cloud.")
101-
# we do not fail, until a key-manager MUST be present
102-
return 0
10359

60+
def _find_secret(conn: openstack.connection.Connection, secret_name_or_id: str):
61+
"""Replacement method for finding secrets.
10462
105-
def check_key_manager_permissions(conn: openstack.connection.Connection
106-
) -> None:
63+
Mimicks the behavior of Connection.key_manager.find_secret()
64+
but fixes an issue with the internal implementation raising an
65+
exception due to an unexpected microversion parameter.
66+
"""
67+
secrets = conn.key_manager.secrets()
68+
for s in secrets:
69+
if s.name == secret_name_or_id or s.id == secret_name_or_id:
70+
return s
71+
72+
73+
def check_key_manager_permissions(conn: openstack.connection.Connection) -> None:
10774
"""
10875
After checking that the current user only has the member and maybe the
10976
reader role, this method verifies that the user with a member role
11077
has sufficient access to the Key Manager API functionality.
11178
"""
11279
secret_name = "scs-member-role-test-secret"
113-
if not check_for_member_role(conn):
114-
logger.warning("Cannot test key-manager permissions. "
115-
"User has wrong roles")
116-
return None
117-
118-
def _find_secret(secret_name_or_id: str):
119-
"""Replacement method for finding secrets.
120-
121-
Mimicks the behavior of Connection.key_manager.find_secret()
122-
but fixes an issue with the internal implementation raising an
123-
exception due to an unexpected microversion parameter.
124-
"""
125-
secrets = conn.key_manager.secrets()
126-
for s in secrets:
127-
if s.name == secret_name_or_id or s.id == secret_name_or_id:
128-
return s
129-
return None
130-
13180
try:
132-
existing_secret = _find_secret(secret_name)
81+
existing_secret = _find_secret(conn, secret_name)
13382
if existing_secret:
13483
conn.key_manager.delete_secret(existing_secret)
13584

13685
conn.key_manager.create_secret(
13786
name=secret_name,
13887
payload_content_type="text/plain",
13988
secret_type="opaque",
140-
payload="foo"
141-
)
142-
143-
new_secret = _find_secret(secret_name)
144-
assert new_secret, (
145-
f"Secret created with name '{secret_name}' was not discoverable by "
146-
f"the user"
89+
payload="foo",
14790
)
148-
conn.key_manager.delete_secret(new_secret)
149-
except openstack.exceptions.ForbiddenException as e:
150-
print(
151-
"Users of the 'member' role can use Key Manager API: FAIL"
91+
try:
92+
new_secret = _find_secret(conn, secret_name)
93+
if not new_secret:
94+
raise ValueError(f"Secret '{secret_name}' was not discoverable by the user")
95+
finally:
96+
conn.key_manager.delete_secret(new_secret)
97+
except openstack.exceptions.ForbiddenException:
98+
logger.debug('exception details', exc_info=True)
99+
logger.error(
100+
"Users with the 'member' role can use Key Manager API: FAIL"
152101
)
153-
print(
154-
f"ERROR: {str(e)}"
155-
)
156-
exit(1)
157-
print(
158-
"Users of the 'member' role can use Key Manager API: PASS"
102+
return 1
103+
logger.info(
104+
"Users with the 'member' role can use Key Manager API: PASS"
159105
)
160106

161107

162108
def main():
163-
parser = argparse.ArgumentParser(
164-
description="SCS Mandatory IaaS Service Checker")
109+
initialize_logging()
110+
parser = argparse.ArgumentParser(description="SCS Mandatory IaaS Service Checker")
165111
parser.add_argument(
166-
"--os-cloud", type=str,
112+
"--os-cloud",
113+
type=str,
167114
help="Name of the cloud from clouds.yaml, alternative "
168-
"to the OS_CLOUD environment variable"
115+
"to the OS_CLOUD environment variable",
169116
)
170117
parser.add_argument(
171-
"--debug", action="store_true",
172-
help="Enable OpenStack SDK debug logging"
118+
"--debug", action="store_true", help="Enable OpenStack SDK debug logging"
173119
)
174120
args = parser.parse_args()
175-
openstack.enable_logging(debug=args.debug)
121+
# @mbuechse: I think this is so much as to be unusable!
122+
# (If necessary, a developer can always uncomment)
123+
# openstack.enable_logging(debug=args.debug)
124+
if args.debug:
125+
logger.setLevel(logging.DEBUG)
176126

177127
# parse cloud name for lookup in clouds.yaml
178-
cloud = os.environ.get("OS_CLOUD", None)
179-
if args.os_cloud:
180-
cloud = args.os_cloud
181-
assert cloud, (
182-
"You need to have the OS_CLOUD environment variable set to your cloud "
183-
"name or pass it via --os-cloud"
184-
)
128+
cloud = args.os_cloud or os.environ.get("OS_CLOUD", None)
129+
if not cloud:
130+
raise RuntimeError(
131+
"You need to have the OS_CLOUD environment variable set to your cloud "
132+
"name or pass it via --os-cloud"
133+
)
185134

186-
return check_presence_of_key_manager(cloud)
135+
with openstack.connect(cloud=cloud) as conn:
136+
if not check_for_member_role(conn):
137+
logger.critical("Cannot test key-manager permissions. User has wrong roles")
138+
return 1
139+
if check_presence_of_key_manager(conn):
140+
return check_key_manager_permissions(conn)
141+
else:
142+
# not an error, because key manager is merely recommended
143+
logger.warning("There is no key-manager endpoint in the cloud.")
187144

188145

189146
if __name__ == "__main__":
190-
main()
147+
try:
148+
sys.exit(main())
149+
except SystemExit:
150+
raise
151+
except BaseException:
152+
logger.critical("exception", exc_info=True)
153+
sys.exit(1)

0 commit comments

Comments
 (0)