-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[9.0] Include DiracX token in proxy PEM files #7261
Changes from all commits
8724ef1
e3ce7d0
849eed0
cfe2fc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
from __future__ import annotations | ||
fstagni marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
__all__ = ( | ||
"DiracXClient", | ||
"diracxTokenFromPEM", | ||
) | ||
|
||
import base64 | ||
import json | ||
import re | ||
import textwrap | ||
from contextlib import contextmanager | ||
from pathlib import Path | ||
from tempfile import NamedTemporaryFile | ||
from typing import Any | ||
|
||
from diracx.client import DiracClient as _DiracClient | ||
from diracx.core.models import TokenResponse | ||
from diracx.core.preferences import DiracxPreferences | ||
from diracx.core.utils import serialize_credentials | ||
|
||
from DIRAC import gConfig, S_ERROR | ||
from DIRAC.ConfigurationSystem.Client.Helpers import Registry | ||
from DIRAC.Core.Security.Locations import getDefaultProxyLocation | ||
from DIRAC.Core.Utilities.ReturnValues import convertToReturnValue, returnValueOrRaise | ||
|
||
|
||
PEM_BEGIN = "-----BEGIN DIRACX-----" | ||
PEM_END = "-----END DIRACX-----" | ||
RE_DIRACX_PEM = re.compile(rf"{PEM_BEGIN}\n(.*)\n{PEM_END}", re.MULTILINE | re.DOTALL) | ||
|
||
|
||
@convertToReturnValue | ||
def addTokenToPEM(pemPath, group): | ||
from DIRAC.Core.Base.Client import Client | ||
|
||
vo = Registry.getVOMSVOForGroup(group) | ||
disabledVOs = gConfig.getValue("/DiracX/DisabledVOs", []) | ||
if vo and vo not in disabledVOs: | ||
token_content = returnValueOrRaise( | ||
Client(url="Framework/ProxyManager", proxyLocation=pemPath).exchangeProxyForToken() | ||
) | ||
|
||
token = TokenResponse( | ||
access_token=token_content["access_token"], | ||
expires_in=token_content["expires_in"], | ||
token_type=token_content.get("token_type"), | ||
refresh_token=token_content.get("refresh_token"), | ||
) | ||
|
||
token_pem = f"{PEM_BEGIN}\n" | ||
data = base64.b64encode(serialize_credentials(token).encode("utf-8")).decode() | ||
token_pem += textwrap.fill(data, width=64) | ||
token_pem += f"\n{PEM_END}\n" | ||
|
||
with open(pemPath, "a") as f: | ||
f.write(token_pem) | ||
|
||
|
||
def diracxTokenFromPEM(pemPath) -> dict[str, Any] | None: | ||
"""Extract the DiracX token from the proxy PEM file""" | ||
pem = Path(pemPath).read_text() | ||
if match := RE_DIRACX_PEM.search(pem): | ||
match = match.group(1) | ||
return json.loads(base64.b64decode(match).decode("utf-8")) | ||
|
||
|
||
@contextmanager | ||
def DiracXClient() -> _DiracClient: | ||
"""Get a DiracX client instance with the current user's credentials""" | ||
diracxUrl = gConfig.getValue("/DiracX/URL") | ||
if not diracxUrl: | ||
raise ValueError("Missing mandatory /DiracX/URL configuration") | ||
|
||
proxyLocation = getDefaultProxyLocation() | ||
diracxToken = diracxTokenFromPEM(proxyLocation) | ||
|
||
with NamedTemporaryFile(mode="wt") as token_file: | ||
token_file.write(json.dumps(diracxToken)) | ||
token_file.flush() | ||
token_file.seek(0) | ||
|
||
pref = DiracxPreferences(url=diracxUrl, credentials_path=token_file.name) | ||
with _DiracClient(diracx_preferences=pref) as api: | ||
yield api |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ | |
from DIRAC.Core.Security import Properties | ||
from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader | ||
from DIRAC.ConfigurationSystem.Client.Helpers import Registry | ||
|
||
from DIRAC.FrameworkSystem.Utilities.diracx import get_token | ||
|
||
DEFAULT_MAIL_FROM = "[email protected]" | ||
|
||
|
@@ -412,7 +412,6 @@ def export_getVOMSProxyWithToken(self, userDN, userGroup, requestPem, requiredLi | |
@convertToReturnValue | ||
def export_exchangeProxyForToken(self): | ||
"""Exchange a proxy for an equivalent token to be used with diracx""" | ||
from DIRAC.FrameworkSystem.Utilities.diracx import get_token | ||
|
||
credDict = self.getRemoteCredentials() | ||
return get_token( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
# pylint: disable=import-error | ||
import requests | ||
|
||
from cachetools import TTLCache, cached | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"""Query DiracX for information about the current user | ||
|
||
This is a stripped down version of the "dirac whoami" script from DiracX. | ||
It primarily exists as a method of validating the current user's credentials are functional. | ||
""" | ||
import json | ||
|
||
from DIRAC.Core.Base.Script import Script | ||
from DIRAC.Core.Security.DiracX import DiracXClient | ||
|
||
|
||
@Script() | ||
def main(): | ||
Script.parseCommandLine() | ||
|
||
with DiracXClient() as api: | ||
user_info = api.auth.userinfo() | ||
print(json.dumps(user_info.as_dict(), indent=2)) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,6 +77,7 @@ def main(): | |
from DIRAC.Core.Security import VOMS | ||
from DIRAC.FrameworkSystem.Client.ProxyManagerClient import gProxyManager | ||
from DIRAC.ConfigurationSystem.Client.Helpers import Registry | ||
from DIRAC.Core.Security.DiracX import DiracXClient | ||
|
||
if params.csEnabled: | ||
retVal = Script.enableCS() | ||
|
@@ -151,6 +152,12 @@ def invalidProxy(msg): | |
invalidProxy(f"Cannot determine life time of VOMS attributes: {result['Message']}") | ||
if int(result["Value"].strip()) == 0: | ||
invalidProxy("VOMS attributes are expired") | ||
# Ensure the proxy is working with DiracX | ||
try: | ||
with DiracXClient() as api: | ||
api.auth.userinfo() | ||
except Exception as e: | ||
invalidProxy(f"Failed to access DiracX: {e}") | ||
Comment on lines
+155
to
+160
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It this absolutely needed at this stage? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If what is absolutely need ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes |
||
|
||
sys.exit(0) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in
dirac_ci.sh
you also addeddiracx-cli
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for normal installation. In
dirac_ci.sh
, we install what's in themain
branchThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only needed for the integration test setup itself