Skip to content

Commit d901fa9

Browse files
committed
[minor] Add validateIBMEntitlementKey() function
1 parent 9e9d25a commit d901fa9

1 file changed

Lines changed: 95 additions & 0 deletions

File tree

src/mas/devops/utils.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
for pre-release versions and wildcard version strings.
66
"""
77

8+
import logging
9+
import re
810
import semver
11+
import requests
12+
from requests.auth import HTTPBasicAuth
13+
14+
logger = logging.getLogger(__name__)
915

1016

1117
def isVersionBefore(_compare_to_version, _current_version):
@@ -74,3 +80,92 @@ def isVersionEqualOrAfter(_compare_to_version, _current_version):
7480
current_version = semver.VersionInfo.parse(strippedVersion)
7581
compareToVersion = semver.VersionInfo.parse(_compare_to_version)
7682
return current_version.compare(compareToVersion) >= 0
83+
84+
85+
def validateIBMEntitlementKey(entitlementKey: str, repository: str = "cp/mas/coreapi", timeout: int = 30) -> bool:
86+
"""Validate IBM entitlement key against cp.icr.io registry.
87+
88+
This function validates an IBM entitlement key by attempting to obtain
89+
an authentication token from the IBM Container Registry and verifying
90+
access to the specified repository.
91+
92+
Args:
93+
entitlementKey (str): IBM entitlement key to validate.
94+
repository (str, optional): Repository to test access against. Defaults to "cp/mas/coreapi".
95+
timeout (int, optional): Request timeout in seconds. Defaults to 30.
96+
97+
Returns:
98+
bool: True if key is valid and grants access to the repository, False otherwise.
99+
100+
Raises:
101+
requests.exceptions.RequestException: If network request fails.
102+
"""
103+
try:
104+
registry_url = f"https://cp.icr.io/v2/{repository}/tags/list"
105+
logger.debug(f"Validating entitlement key against {repository}")
106+
107+
# First request without auth to get the auth challenge
108+
response = requests.get(registry_url, timeout=timeout)
109+
110+
if response.status_code == 401:
111+
# Parse WWW-Authenticate header to get token endpoint
112+
auth_header = response.headers.get("WWW-Authenticate", "")
113+
logger.debug(f"Auth challenge received: {auth_header[:100]}...")
114+
115+
# Extract realm and service from auth header
116+
realm_match = re.search(r'realm="([^"]+)"', auth_header)
117+
service_match = re.search(r'service="([^"]+)"', auth_header)
118+
scope_match = re.search(r'scope="([^"]+)"', auth_header)
119+
120+
if not realm_match:
121+
logger.error("Could not parse authentication realm")
122+
return False
123+
124+
token_url = realm_match.group(1)
125+
params = {}
126+
127+
if service_match:
128+
params["service"] = service_match.group(1)
129+
if scope_match:
130+
params["scope"] = scope_match.group(1)
131+
else:
132+
params["scope"] = f"repository:{repository}:pull"
133+
134+
logger.debug(f"Token endpoint: {token_url}")
135+
136+
# Get authentication token
137+
token_response = requests.get(token_url, params=params, auth=HTTPBasicAuth("cp", entitlementKey), timeout=timeout)
138+
139+
if token_response.status_code != 200:
140+
logger.error(f"Failed to get token (HTTP {token_response.status_code})")
141+
return False
142+
143+
token_data = token_response.json()
144+
token = token_data.get("token") or token_data.get("access_token")
145+
146+
if not token:
147+
logger.error("No token received - invalid entitlement key")
148+
return False
149+
150+
# Validate token by accessing registry
151+
logger.debug("Validating token against registry")
152+
headers = {"Authorization": f"Bearer {token}"}
153+
validate_response = requests.get(registry_url, headers=headers, timeout=timeout)
154+
155+
if validate_response.status_code == 200:
156+
logger.info(f"Valid entitlement key with access to {repository}")
157+
return True
158+
else:
159+
logger.error(f"Token validation failed (HTTP {validate_response.status_code})")
160+
return False
161+
162+
elif response.status_code == 200:
163+
logger.info("Registry accessible without authentication (public repository)")
164+
return True
165+
else:
166+
logger.error(f"Unexpected response (HTTP {response.status_code})")
167+
return False
168+
169+
except requests.exceptions.RequestException as e:
170+
logger.error(f"Request failed: {e}")
171+
raise

0 commit comments

Comments
 (0)