Skip to content

Commit c7f9bb0

Browse files
authored
Defuse scs-0123-swift-s3 (#1204)
This testcase has multiple problems: - It is not mentioned in the normative part of the standard (only in the section on testing) - What user demand it is supposed to satisfy is unclear - After recent updates to Keystone this no longer works with restricted (normal) application credentials Its benefit (if any) does not outweigh its cost. Further changes: * Remove confusing (arguably non-normative) statement from scs-0123-v1 * Add Tests/iaas/CHANGELOG --------- Signed-off-by: Matthias Büchse <matthias.buechse@alasca.cloud>
1 parent b82017f commit c7f9bb0

4 files changed

Lines changed: 25 additions & 129 deletions

File tree

Standards/scs-0123-v1-mandatory-and-supported-IaaS-services.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,3 @@ The SCS standard offers no guarantees for compatibility or reliability of servic
7979
## Conformance Tests
8080

8181
The presence of the mandatory OpenStack APIs will be tested in [this test-script](https://github.com/SovereignCloudStack/standards/blob/main/Tests/iaas/scs_0123_mandatory_services/mandatory_services.py)
82-
The test will further check whether the object-store endpoint is compatible to s3.

Standards/scs-0123-w1-mandatory-and-supported-IaaS-services-implementation.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ the following testcases in accordance with the standard:
1515
- `scs-0123-service-<type>` ensures that a service with the given type can be found in the service catalog
1616
- `scs-0123-storage-apis` ensures that a service of one of the following types can be found: "volume", "volumev3", "block-storage"
1717

18-
### Deprecated tests
18+
v1 only (soon to be deprecated):
1919

20-
#### v1 only
20+
- `scs-0123-swift-s3` ensures that S3 is present on the same host as swift
2121

22-
- `scs-0123-swift-s3` ensures that S3 can be used to access object storage using EC2 credentials from the identity API
22+
Note: this testcase is a rather weak surrogate for testing the presence of S3 in general, which is resolved with v2,
23+
where a dedicated entry in the service catalog is mandated.

Tests/iaas/CHANGELOG

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# IaaS test changelog
2+
3+
## 2026-06-11
4+
5+
- Relaxed `scs-0123-swift-s3`:
6+
no longer access S3 via EC2 credentials from Keystone;
7+
rather, only check that the S3 API is present.
Lines changed: 14 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import json
21
import logging
32
import re
4-
import uuid
53

64
import boto3
75
import openstack
86

97

10-
TESTCONTNAME = "scs-test-container"
11-
EC2MARKER = "TmpMandSvcTest"
8+
HOST_REGEX = re.compile(r"^(https*://[^/]*)/")
129

1310
logger = logging.getLogger(__name__)
1411
# NOTE suppress excessive logging (who knows what sensitive data might be in there)
@@ -45,131 +42,23 @@ def s3_conn(creds, conn):
4542
)
4643

4744

48-
def _parse_blob(cred):
49-
try:
50-
return json.loads(cred.blob)
51-
except Exception as exc:
52-
logger.debug(f"unable to parse credential {cred!r}: {exc!r}")
53-
return None
54-
55-
56-
def get_usable_credentials(conn):
57-
"""
58-
get all ec2 credentials for this project that carry meaningful data
59-
60-
returns list of pairs (credential, parsed ec2 data)
61-
"""
62-
project_id = conn.identity.get_project_id()
63-
candidates = [
64-
(cred, _parse_blob(cred))
65-
for cred in conn.identity.credentials()
66-
if cred.type == "ec2" and cred.project_id == project_id
67-
]
68-
return [
69-
(cred, parsed)
70-
for cred, parsed in candidates
71-
if parsed and parsed.get('access') and parsed.get('secret')
72-
]
73-
74-
75-
def remove_leftovers(usable_credentials, conn):
76-
"""
77-
makes sure to delete any leftover set of ec2 credentials
78-
"""
79-
result = []
80-
for item in usable_credentials:
81-
cred, parsed = item
82-
if parsed.get("owner") == EC2MARKER:
83-
logger.debug(f"Removing leftover credential {parsed['access']}")
84-
conn.identity.delete_credential(cred)
85-
else:
86-
result.append(item)
87-
return result
88-
89-
90-
def ensure_ec2_credentials(usable_credentials, conn):
91-
if usable_credentials:
92-
return usable_credentials
93-
parsed = {
94-
"access": uuid.uuid4().hex,
95-
"secret": uuid.uuid4().hex,
96-
"owner": EC2MARKER,
97-
}
98-
blob = json.dumps(parsed)
99-
try:
100-
crd = conn.identity.create_credential(
101-
type="ec2", blob=blob, user_id=conn.current_user_id, project_id=conn.current_project_id,
102-
)
103-
except BaseException:
104-
logger.warning("ec2 creds creation failed", exc_info=True)
105-
raise
106-
usable_credentials.append((crd, parsed))
107-
return usable_credentials # also return for chaining
108-
109-
110-
def s3_from_ostack(usable_credentials, conn, rgx=re.compile(r"^(https*://[^/]*)/")):
111-
"""Set creds from openstack swift/keystone"""
112-
# just use the first usable set of ec2 credentials
113-
_, parsed = usable_credentials[0]
114-
s3_creds = {
115-
"AK": parsed["access"],
116-
"SK": parsed["secret"],
117-
}
118-
m = rgx.match(conn.object_store.get_endpoint())
119-
if m:
120-
s3_creds["HOST"] = m.group(1)
121-
return s3_creds
122-
123-
12445
def compute_scs_0123_swift_s3(services_lookup, conn: openstack.connection.Connection):
12546
"""
126-
This test ensures that S3 can be used to access object storage using EC2 credentials from the identity API.
47+
This test ensures that S3 is present next to Swift.
12748
"""
12849
if 'object-store' not in services_lookup:
12950
logger.info('skipping scs-0123-swift-s3 because object-store not present')
13051
return True
131-
# we assume s3 is accessible via the service catalog, and Swift might exist too
132-
usable_credentials = []
133-
s3_buckets = []
134-
# Get S3 endpoint (swift) and ec2 creds from OpenStack (keystone)
52+
m = HOST_REGEX.match(conn.object_store.get_endpoint())
53+
s3_creds = {
54+
"AK": "doesnotexist",
55+
"SK": "",
56+
"HOST": m and m.group(1),
57+
}
13558
try:
136-
usable_credentials = remove_leftovers(get_usable_credentials(conn), conn)
137-
# we could use any credential from the list usable_credentials, but let's not do that,
138-
# because they could be stale
139-
del usable_credentials[:]
140-
s3_creds = s3_from_ostack(ensure_ec2_credentials(usable_credentials, conn), conn)
141-
142-
# This is to be used for local debugging purposes ONLY
143-
# logger.debug(f"using credentials {s3_creds}")
144-
145-
s3 = s3_conn(s3_creds, conn)
146-
buckets = list(s3.buckets.all())
147-
if not buckets:
148-
s3.create_bucket(Bucket=TESTCONTNAME)
149-
buckets = list(s3.buckets.all())
150-
if not buckets:
151-
raise RuntimeError("failed to create S3 bucket")
152-
153-
# actual test: buckets must equal containers (sort in case the order is different)
154-
s3_buckets = sorted([b.name for b in buckets])
155-
sw_containers = sorted([c.name for c in conn.object_store.containers()])
156-
if s3_buckets == sw_containers:
157-
return True
158-
logger.error(
159-
"S3 buckets and Swift containers differ:\n"
160-
f"S3: {s3_buckets}\n"
161-
f"SW: {sw_containers}"
162-
)
163-
return False
164-
finally:
165-
# Cleanup created S3 bucket
166-
if TESTCONTNAME in s3_buckets:
167-
# contrary to Boto docs at
168-
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/delete_bucket.html
169-
# , the following fails with
170-
# AttributeError: 's3.ServiceResource' object has no attribute 'delete_bucket'. Did you mean: 'create_bucket'?
171-
# s3.delete_bucket(Bucket=TESTCONTNAME)
172-
# workaround:
173-
s3.Bucket(name=TESTCONTNAME).delete()
174-
# Clean up ec2 cred IF we created one
175-
remove_leftovers(usable_credentials, conn)
59+
list(s3_conn(s3_creds, conn).buckets.all())
60+
except Exception as error:
61+
txt = str(error)
62+
return "InvalidAccessKeyId" in txt or "NoSuchKey" in txt
63+
else:
64+
raise RuntimeError("this can not happen")

0 commit comments

Comments
 (0)