Skip to content

Commit 6c4efbc

Browse files
Robin Van de MerghelRobin-Van-de-Merghel
authored andcommitted
feat: Adding JWT support alongside X509 auth
1 parent 746d6b4 commit 6c4efbc

File tree

2 files changed

+132
-19
lines changed

2 files changed

+132
-19
lines changed

Pilot/pilotTools.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@
1818
from functools import partial, wraps
1919
from threading import RLock
2020

21+
try:
22+
from Pilot.proxyTools import (
23+
X509BasedRequest
24+
)
25+
except ImportError:
26+
from proxyTools import (
27+
X509BasedRequest
28+
)
29+
2130
############################
2231
# python 2 -> 3 "hacks"
2332
try:
@@ -700,26 +709,20 @@ def sendMessage(url, pilotUUID, wnVO, method, rawMessage):
700709
caPath = os.getenv("X509_CERT_DIR")
701710
cert = os.getenv("X509_USER_PROXY")
702711

703-
context = ssl.create_default_context()
704-
context.load_verify_locations(capath=caPath)
705-
712+
706713
message = json.dumps((json.dumps(rawMessage), pilotUUID, wnVO))
707714

708-
try:
709-
context.load_cert_chain(cert) # this is a proxy
710-
raw_data = {"method": method, "args": message}
711-
except IsADirectoryError: # assuming it'a dir containing cert and key
712-
context.load_cert_chain(os.path.join(cert, "hostcert.pem"), os.path.join(cert, "hostkey.pem"))
713-
raw_data = {"method": method, "args": message, "extraCredentials": '"hosts"'}
714-
715-
if sys.version_info[0] == 3:
716-
data = urlencode(raw_data).encode("utf-8") # encode to bytes ! for python3
717-
else:
718-
# Python2
719-
data = urlencode(raw_data)
720-
721-
res = urlopen(url, data, context=context)
722-
res.close()
715+
raw_data = {"method": method, "args": message}
716+
717+
X509config = X509BasedRequest(url=url,
718+
caPath=caPath,
719+
certEnv=cert)
720+
721+
# Do the request
722+
_res = X509config.executeRequest(raw_data=raw_data,
723+
headers={
724+
"User-Agent": X509config.generateUserAgent(pilotUUID=pilotUUID)
725+
})
723726

724727

725728
class CommandBase(object):

Pilot/proxyTools.py

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
1-
"""few functions for dealing with proxies"""
1+
"""few functions for dealing with proxies and authentication"""
22

33
from __future__ import absolute_import, division, print_function
44

55
import re
66
from base64 import b16decode
77
from subprocess import PIPE, Popen
8+
import ssl
9+
import sys
10+
import os
11+
12+
try:
13+
IsADirectoryError # pylint: disable=used-before-assignment
14+
except NameError:
15+
IsADirectoryError = OSError
16+
17+
try:
18+
from urllib.parse import urlencode
19+
from urllib.request import Request, urlopen
20+
except ImportError:
21+
from urllib import urlencode
22+
23+
from urllib2 import urlopen
24+
825

926
VOMS_FQANS_OID = b"1.3.6.1.4.1.8005.100.100.4"
1027
VOMS_EXTENSION_OID = b"1.3.6.1.4.1.8005.100.100.5"
@@ -65,3 +82,96 @@ def getVO(proxy_data):
6582
if match:
6683
return match.groups()[0].decode()
6784
raise NotImplementedError("Something went very wrong")
85+
86+
87+
class BaseConnectedRequest(object):
88+
"""This class helps supporting multiple kinds of requests that requires connections"""
89+
90+
def __init__(self, url, caPath, name="unknown"):
91+
self.name = name
92+
self.url = url
93+
self.caPath = caPath
94+
# We assume we have only one context, so this variable could be shared to avoid opening n times a cert.
95+
# On the contrary, to avoid race conditions, we do avoid using "self.data" and "self.headers"
96+
self._context = None
97+
98+
self._prepareRequest()
99+
100+
def generateUserAgent(self, pilotUUID):
101+
"""To analyse the traffic, we can send a taylor-made User-Agent
102+
103+
Args:
104+
pilotUUID (str): Unique ID of the Pilot
105+
106+
Returns:
107+
str: The generated user agent
108+
"""
109+
return "Dirac Pilot [%s]" % pilotUUID
110+
111+
def _prepareRequest(self):
112+
"""As previously, loads the SSL certificates of the server (to avoid "unknown issuer")"""
113+
# Load the SSL context
114+
self._context = ssl.create_default_context()
115+
self._context.load_verify_locations(capath=self.caPath)
116+
117+
def executeRequest(self, raw_data, headers={"User-Agent": "Dirac Pilot [Unknown ID]"}):
118+
"""Execute a HTTP request with the data, headers, and the pre-defined data (SSL + auth)
119+
120+
Args:
121+
raw_data (dict): Data to send
122+
headers (dict, optional): Headers to send, helps to track requests. Defaults to {"User-Agent": "Dirac Pilot [Unknown ID]"}.
123+
124+
Returns:
125+
str: Response of the HTTP request
126+
"""
127+
if sys.version_info[0] == 3:
128+
data = urlencode(raw_data).encode("utf-8") # encode to bytes ! for python3
129+
else:
130+
# Python2
131+
data = urlencode(raw_data)
132+
133+
request = Request(self.url, data=data, headers=headers)
134+
135+
res = urlopen(request, context=self._context)
136+
res.close()
137+
138+
return res.read()
139+
140+
141+
class TokenBasedRequest(BaseConnectedRequest):
142+
"""Connected Request with JWT support"""
143+
144+
def __init__(self, url, caPath, jwtData):
145+
super(TokenBasedRequest, self).__init__(url, caPath, "TokenBasedConnection")
146+
147+
self.jwtData = jwtData
148+
149+
def executeRequest(self, raw_data, headers={"User-Agent": "Dirac Pilot [Unknown ID]"}):
150+
# Adds the JWT in the HTTP request (in the Bearer field)
151+
headers["Bearer"] = self.jwtData
152+
return super(TokenBasedRequest, self).executeRequest(raw_data, headers)
153+
154+
155+
class X509BasedRequest(BaseConnectedRequest):
156+
"""Connected Request with X509 support"""
157+
158+
def __init__(self, url, caPath, certEnv):
159+
super(X509BasedRequest, self).__init__(url, caPath, "X509BasedConnection")
160+
161+
self.certEnv = certEnv
162+
self._hasExtraCredentials = False
163+
164+
# Load X509 once
165+
try:
166+
self._context.load_cert_chain(self.certEnv)
167+
except IsADirectoryError: # assuming it'a dir containing cert and key
168+
self._context.load_cert_chain(
169+
os.path.join(self.certEnv, "hostcert.pem"), os.path.join(self.certEnv, "hostkey.pem")
170+
)
171+
self._hasExtraCredentials = True
172+
173+
def executeRequest(self, raw_data, headers={"User-Agent": "Dirac Pilot [Unknown ID]"}):
174+
# Adds a flag if the passed cert is a Directory
175+
if self._hasExtraCredentials:
176+
raw_data["extraCredentials"] = '"hosts"'
177+
return super(X509BasedRequest, self).executeRequest(raw_data, headers)

0 commit comments

Comments
 (0)