Skip to content

Commit 15e74e7

Browse files
Robin Van de MerghelRobin-Van-de-Merghel
authored andcommitted
feat: Adding JWT support alongside X509 auth
1 parent 93e42b2 commit 15e74e7

File tree

2 files changed

+130
-20
lines changed

2 files changed

+130
-20
lines changed

Pilot/pilotTools.py

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

22+
try:
23+
from Pilot.proxyTools import (
24+
X509BasedRequest
25+
)
26+
except ImportError:
27+
from proxyTools import (
28+
X509BasedRequest
29+
)
30+
2231
############################
2332
# python 2 -> 3 "hacks"
2433
try:
@@ -676,28 +685,20 @@ def sendMessage(url, pilotUUID, wnVO, method, rawMessage):
676685
caPath = os.getenv("X509_CERT_DIR")
677686
cert = os.getenv("X509_USER_PROXY")
678687

679-
context = ssl.create_default_context()
680-
context.load_verify_locations(capath=caPath)
681688

682689
message = json.dumps((json.dumps(rawMessage), pilotUUID, wnVO))
683690

684-
try:
685-
context.load_cert_chain(cert) # this is a proxy
686-
raw_data = {"method": method, "args": message}
687-
except IsADirectoryError: # assuming it'a dir containing cert and key
688-
context.load_cert_chain(
689-
os.path.join(cert, "hostcert.pem"),
690-
os.path.join(cert, "hostkey.pem")
691-
)
692-
raw_data = {"method": method, "args": message, "extraCredentials": '"hosts"'}
693-
694-
if sys.version_info[0] == 3:
695-
data = urlencode(raw_data).encode("utf-8") # encode to bytes ! for python3
696-
else:
697-
data = urlencode(raw_data)
691+
raw_data = {"method": method, "args": message}
698692

699-
res = urlopen(url, data, context=context)
700-
res.close()
693+
X509config = X509BasedRequest(url=url,
694+
caPath=caPath,
695+
certEnv=cert)
696+
697+
# Do the request
698+
_res = X509config.executeRequest(raw_data=raw_data,
699+
headers={
700+
"User-Agent": X509config.generateUserAgent(pilotUUID=pilotUUID)
701+
})
701702

702703

703704
class CommandBase(object):

Pilot/proxyTools.py

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
1-
""" few functions for dealing with proxies
2-
"""
1+
"""few functions for dealing with proxies and authentification"""
32

43
from __future__ import absolute_import, division, print_function
54

65
import re
76
from base64 import b16decode
87
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+
925

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

0 commit comments

Comments
 (0)