Skip to content

Commit 88270c0

Browse files
committed
feat: Add support for DiracX tokens, and possibly tokens in proxies
1 parent a3edb9a commit 88270c0

5 files changed

Lines changed: 536 additions & 63 deletions

File tree

.github/workflows/basic.yml

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@ name: Basic tests
33
on: [push, pull_request]
44

55
jobs:
6-
76
shellcheck:
87
runs-on: ubuntu-24.04
98
if: github.event_name != 'push' || github.repository == 'DIRACGrid/DIRAC'
109
timeout-minutes: 10
1110

1211
steps:
13-
- uses: actions/checkout@v3
14-
- name: Run shellcheck
15-
run: |
16-
find tests/CI -name '*.sh' -print0 | xargs -0 -n1 shellcheck --external-sources;
12+
- uses: actions/checkout@v3
13+
- name: Run shellcheck
14+
run: |
15+
find tests/CI -name '*.sh' -print0 | xargs -0 -n1 shellcheck --external-sources;
1716
1817
pycodestyle:
1918
runs-on: ubuntu-24.04
@@ -23,27 +22,26 @@ jobs:
2322
strategy:
2423
fail-fast: false
2524
matrix:
26-
python:
25+
python:
2726
- 3.6.15
2827
- 3.9.17
2928

3029
container: python:${{ matrix.python }}-slim
3130
steps:
32-
- uses: actions/checkout@v3
33-
- name: Installing dependencies
34-
run: |
35-
python -m pip install pycodestyle
36-
- name: Run pycodestyle
37-
run: |
38-
if [[ "${REFERENCE_BRANCH}" != "" ]]; then
39-
git remote add upstream https://github.com/DIRACGrid/Pilot.git
40-
git fetch --no-tags upstream "${REFERENCE_BRANCH}"
41-
git branch -vv
42-
git diff -U0 "upstream/${REFERENCE_BRANCH}" | pycodestyle --diff
43-
fi
44-
env:
45-
REFERENCE_BRANCH: ${{ github['base_ref'] || github['head_ref'] }}
46-
31+
- uses: actions/checkout@v3
32+
- name: Installing dependencies
33+
run: |
34+
python -m pip install pycodestyle
35+
- name: Run pycodestyle
36+
run: |
37+
if [[ "${REFERENCE_BRANCH}" != "" ]]; then
38+
git remote add upstream https://github.com/DIRACGrid/Pilot.git
39+
git fetch --no-tags upstream "${REFERENCE_BRANCH}"
40+
git branch -vv
41+
git diff -U0 "upstream/${REFERENCE_BRANCH}" | pycodestyle --diff
42+
fi
43+
env:
44+
REFERENCE_BRANCH: ${{ github['base_ref'] || github['head_ref'] }}
4745

4846
pytest:
4947
runs-on: ubuntu-24.04
@@ -53,23 +51,22 @@ jobs:
5351
strategy:
5452
fail-fast: false
5553
matrix:
56-
python:
54+
python:
5755
- 3.6.15
5856
- 3.9.17
5957

6058
container: python:${{ matrix.python }}-slim
6159
steps:
62-
- uses: actions/checkout@v3
63-
- name: Installing dependencies
64-
run: |
65-
echo 'deb http://archive.debian.org/debian stretch main' > /etc/apt/sources.list
66-
echo 'deb http://archive.debian.org/debian-security stretch/updates main' >> /etc/apt/sources.list
67-
apt-get update || true
68-
python -m pip install pytest mock
69-
apt install -y voms-clients
70-
- name: Run pytest
71-
run: pytest
72-
60+
- uses: actions/checkout@v3
61+
- name: Installing dependencies
62+
run: |
63+
echo 'deb http://archive.debian.org/debian stretch main' > /etc/apt/sources.list
64+
echo 'deb http://archive.debian.org/debian-security stretch/updates main' >> /etc/apt/sources.list
65+
apt-get update || true
66+
python -m pip install pytest mock
67+
apt install -y voms-clients
68+
- name: Run pytest
69+
run: pytest
7370

7471
pylint:
7572
runs-on: ubuntu-24.04
@@ -79,15 +76,15 @@ jobs:
7976
strategy:
8077
fail-fast: false
8178
matrix:
82-
python:
79+
python:
8380
- 3.6.15
8481
- 3.9.17
8582

8683
container: python:${{ matrix.python }}-slim
8784
steps:
88-
- uses: actions/checkout@v3
89-
- name: Installing dependencies
90-
run: |
91-
python -m pip install pylint
92-
- name: Run pylint
93-
run: pylint -E Pilot/
85+
- uses: actions/checkout@v3
86+
- name: Installing dependencies
87+
run: |
88+
python -m pip install pylint
89+
- name: Run pylint
90+
run: pylint -E Pilot/

Pilot/dirac-pilot.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@
3131
getCommand,
3232
pythonPathCheck,
3333
)
34-
35-
############################
34+
from proxyTools import revokePilotToken
3635

3736
if __name__ == "__main__":
3837
pilotStartTime = int(time.time())
@@ -83,7 +82,9 @@
8382
log.debug("PARAMETER [%s]" % ", ".join(map(str, pilotParams.optList)))
8483

8584
if pilotParams.commandExtensions:
86-
log.info("Requested command extensions: %s" % str(pilotParams.commandExtensions))
85+
log.info(
86+
"Requested command extensions: %s" % str(pilotParams.commandExtensions)
87+
)
8788

8889
log.info("Executing commands: %s" % str(pilotParams.commands))
8990

@@ -107,3 +108,15 @@
107108
if remote:
108109
log.buffer.flush()
109110
sys.exit(-1)
111+
112+
log.info("Pilot tasks finished.")
113+
114+
if pilotParams.jwt:
115+
if not pilotParams.isLegacyPilot:
116+
log.info("Revoking pilot token.")
117+
revokePilotToken(
118+
pilotParams.diracXServer,
119+
pilotParams.pilotUUID,
120+
pilotParams.jwt,
121+
pilotParams.clientID,
122+
)

Pilot/pilotCommands.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,20 @@ def __init__(self, pilotParams):
526526

527527
@logFinalizer
528528
def execute(self):
529-
"""Calls dirac-admin-add-pilot"""
529+
"""Calls dirac-admin-add-pilot
530+
531+
Deprecated in DIRAC V8, new mechanism in V9 and DiracX."""
532+
533+
if self.pp.jwt:
534+
if not self.pp.isLegacyPilot:
535+
self.log.warn("Skipping module, normally it is already done via DiracX secret-exchange.")
536+
return
537+
538+
# If we're here, this is a legacy pilot with a DiracX token embedded in it.
539+
# TODO: See if we do a dirac-admin-add-pilot in DiracX for legacy pilots
540+
else:
541+
# If we're here, this is a DIRAC only pilot without diracX token embedded in it.
542+
pass
530543

531544
if not self.pp.pilotReference:
532545
self.log.warn("Skipping module, no pilot reference found")
@@ -1210,3 +1223,4 @@ def execute(self):
12101223
"""Standard entry point to a pilot command"""
12111224
self._setNagiosOptions()
12121225
self._runNagiosProbes()
1226+

Pilot/pilotTools.py

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@
2222
from urllib.parse import urlencode
2323
from urllib.request import urlopen
2424

25-
from proxyTools import getVO
26-
27-
# Utilities functions
25+
from proxyTools import BaseRequest, extract_diracx_payload, getVO
2826

2927

3028
def load_module_from_path(module_name, path_to_module):
@@ -884,10 +882,14 @@ def __init__(self):
884882
self.setup = ""
885883
self.configServer = ""
886884
self.preferredURLPatterns = ""
885+
self.diracXServer = ""
887886
self.ceName = ""
888887
self.ceType = ""
889888
self.queueName = ""
890889
self.gridCEType = ""
890+
self.pilotSecret = ""
891+
self.clientID = ""
892+
self.jwt = {}
891893
# maxNumberOfProcessors: the number of
892894
# processors allocated to the pilot which the pilot can allocate to one payload
893895
# used to set payloadProcessors unless other limits are reached (like the number of processors on the WN)
@@ -922,6 +924,7 @@ def __init__(self):
922924
self.pilotCFGFile = "pilot.json"
923925
self.pilotLogging = False
924926
self.loggerURL = None
927+
self.isLegacyPilot = False
925928
self.loggerTimerInterval = 0
926929
self.loggerBufsize = 1000
927930
self.pilotUUID = "unknown"
@@ -978,6 +981,7 @@ def __init__(self):
978981
("y:", "CEType=", "CE Type (normally InProcess)"),
979982
("z", "pilotLogging", "Activate pilot logging system"),
980983
("C:", "configurationServer=", "Configuration servers to use"),
984+
("", "diracx_URL=", "DiracX Server URL to use"),
981985
("D:", "disk=", "Require at least <space> MB available"),
982986
("E:", "commandExtensions=", "Python modules with extra commands"),
983987
("F:", "pilotCFGFile=", "Specify pilot CFG file"),
@@ -1015,6 +1019,8 @@ def __init__(self):
10151019
),
10161020
("", "architectureScript=", "architecture script to use"),
10171021
("", "CVMFS_locations=", "comma-separated list of CVMS locations"),
1022+
("", "pilotSecret=", "secret that the pilot uses with DiracX"),
1023+
("", "clientID=", "client id used by DiracX to revoke a token"),
10181024
)
10191025

10201026
# Possibly get Setup and JSON URL/filename from command line
@@ -1041,6 +1047,74 @@ def __init__(self):
10411047
self.installEnv["X509_USER_PROXY"] = self.certsLocation
10421048
os.environ["X509_USER_PROXY"] = self.certsLocation
10431049

1050+
try:
1051+
self.__get_diracx_jwt()
1052+
except Exception as e:
1053+
self.log.error("Error setting DiracX: %s" % e)
1054+
# Remove all settings to prevent using it.
1055+
self.diracXServer = None
1056+
self.pilotSecret = None
1057+
self.loggerURL = None
1058+
self.jwt = {}
1059+
self.log.error("Won't use DiracX.")
1060+
1061+
def __get_diracx_jwt(self):
1062+
# Pilot auth: two cases
1063+
# 1. Has a secret (DiracX Pilot), exchange for a token
1064+
# 2. Legacy Pilot, has a proxy with a DiracX section in it (extract the jwt from it)
1065+
if self.pilotUUID and self.pilotSecret and self.diracXServer:
1066+
self.log.info("Fetching JWT in DiracX (URL: %s)" % self.diracXServer)
1067+
1068+
config = BaseRequest(
1069+
"%s/api/auth/secret-exchange" % (self.diracXServer),
1070+
os.getenv("X509_CERT_DIR"),
1071+
self.pilotUUID,
1072+
)
1073+
1074+
try:
1075+
self.jwt = config.executeRequest(
1076+
{"pilot_stamp": self.pilotUUID, "pilot_secret": self.pilotSecret}
1077+
)
1078+
except HTTPError as e:
1079+
self.log.error("Request failed: %s" % str(e))
1080+
self.log.error("Could not fetch pilot tokens.")
1081+
if e.code == 401:
1082+
# First test if the error occurred because of "bad pilot_stamp"
1083+
# If so, this pilot is in the vacuum case
1084+
# So we redo auth, but this time with the right data for vacuum cases
1085+
self.log.error("Retrying with vacuum case data...")
1086+
self.jwt = config.executeRequest(
1087+
{
1088+
"pilot_stamp": self.pilotUUID,
1089+
"pilot_secret": self.pilotSecret,
1090+
"vo": self.wnVO,
1091+
"grid_type": self.gridCEType,
1092+
"grid_site": self.site,
1093+
"status": "Running",
1094+
}
1095+
)
1096+
else:
1097+
raise RuntimeError("Can't be a vacuum case.")
1098+
1099+
self.log.info("Fetched the pilot token with the pilot secret.")
1100+
self.isLegacyPilot = False
1101+
elif self.pilotUUID and self.diracXServer:
1102+
# Try to extract a token for proxy
1103+
self.log.info("Trying to extract diracx token from proxy.")
1104+
1105+
cert = os.getenv("X509_USER_PROXY")
1106+
if cert:
1107+
with open(cert, "rb") as fp:
1108+
self.jwt = extract_diracx_payload(fp.read())
1109+
self.isLegacyPilot = True
1110+
self.log.info("Successfully extracted token from proxy.")
1111+
else:
1112+
raise RuntimeError("Could not locate a proxy via X509_USER_PROXY")
1113+
else:
1114+
self.log.info(
1115+
"PilotUUID, pilotSecret, and diracXServer are needed to support DiracX."
1116+
)
1117+
10441118
def __setSecurityDir(self, envName, dirLocation):
10451119
"""Set the environment variable of the `envName`, and add it also to the Pilot Parameters
10461120
@@ -1152,6 +1226,8 @@ def __initCommandLine2(self):
11521226
self.keepPythonPath = True
11531227
elif o in ("-C", "--configurationServer"):
11541228
self.configServer = v
1229+
elif o == "--diracx_URL":
1230+
self.diracXServer = v
11551231
elif o in ("-G", "--Group"):
11561232
self.userGroup = v
11571233
elif o in ("-x", "--execute"):
@@ -1225,6 +1301,10 @@ def __initCommandLine2(self):
12251301
self.architectureScript = v
12261302
elif o == "--CVMFS_locations":
12271303
self.CVMFS_locations = v.split(",")
1304+
elif o == "--pilotSecret":
1305+
self.pilotSecret = v
1306+
elif o == "--clientID":
1307+
self.clientID = v
12281308

12291309
def __loadJSON(self):
12301310
"""

0 commit comments

Comments
 (0)