Skip to content

Commit 41f4b17

Browse files
authored
Merge pull request #5901 from TaykYoku/8.0_fixOAuth-5
[integration] move ProxyManager to HTTPs
2 parents a9feee2 + cece7cc commit 41f4b17

File tree

12 files changed

+205
-134
lines changed

12 files changed

+205
-134
lines changed

docs/source/AdministratorGuide/Configuration/ConfReference/Systems/Framework/Agents/MyProxyRenewalAgent/index.rst

Lines changed: 0 additions & 19 deletions
This file was deleted.

docs/source/AdministratorGuide/Configuration/ConfReference/Systems/Framework/Agents/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,3 @@ Agents associated with Framework System:
3939
:maxdepth: 2
4040

4141
CAUpdateAgent/index
42-
MyProxyRenewalAgent/index

src/DIRAC/ConfigurationSystem/test/Test_agentOptions.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@
1717
("DIRAC.DataManagementSystem.Agent.FTS3Agent", {}),
1818
("DIRAC.FrameworkSystem.Agent.CAUpdateAgent", {}),
1919
("DIRAC.FrameworkSystem.Agent.ComponentSupervisionAgent", {}),
20-
(
21-
"DIRAC.FrameworkSystem.Agent.MyProxyRenewalAgent",
22-
{"IgnoreOptions": ["MinValidity", "ValidityPeriod", "MinimumLifeTime", "RenewedLifeTime"]},
23-
),
20+
("DIRAC.FrameworkSystem.Agent.ProxyRenewalAgent", {}),
2421
("DIRAC.RequestManagementSystem.Agent.CleanReqDBAgent", {}),
2522
(
2623
"DIRAC.RequestManagementSystem.Agent.RequestExecutingAgent",

src/DIRAC/Core/Tornado/Server/private/BaseRequestHandler.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
"""DIRAC server has various passive components listening to incoming client requests and reacting accordingly by serving requested information, such as **services** or **APIs**. This module is basic for each of these components and describes the basic concept of access to them.
1+
"""DIRAC server has various passive components listening to incoming client requests
2+
and reacting accordingly by serving requested information,
3+
such as **services** or **APIs**.
4+
5+
This module is basic for each of these components and describes the basic concept of access to them.
26
"""
37
import time
48
import inspect
@@ -145,7 +149,8 @@ def _runActions(self, reqObj):
145149

146150

147151
class BaseRequestHandler(RequestHandler):
148-
"""This class primarily describes the process of processing an incoming request and the methods of authentication and authorization.
152+
"""This class primarily describes the process of processing an incoming request
153+
and the methods of authentication and authorization.
149154
150155
Each HTTP request is served by a new instance of this class.
151156
@@ -168,7 +173,8 @@ class BaseRequestHandler(RequestHandler):
168173
{TornadoService, TornadoREST} -> BaseRequestHandler;
169174
BaseRequestHandler -> RequestHandler [label=" inherit", fontsize=8];
170175
171-
In order to create a class that inherits from ``BaseRequestHandler``, first you need to determine what HTTP methods need to be supported.
176+
In order to create a class that inherits from ``BaseRequestHandler``,
177+
first you need to determine what HTTP methods need to be supported.
172178
Override the class variable ``SUPPORTED_METHODS`` by writing down the necessary methods there.
173179
Note that by default all HTTP methods are supported.
174180
@@ -182,8 +188,10 @@ class BaseRequestHandler(RequestHandler):
182188
- ``auth_<method name>`` describes authorization rules for a single method and has higher priority than ``DEFAULT_AUTHORIZATION``
183189
- ``METHOD_PREFIX`` helps in finding the target method, see the :py:meth:`_getMethod` methods, where described how exactly.
184190
185-
It is worth noting that DIRAC supports several ways to authorize the request and they are all descriptive in ``DEFAULT_AUTHENTICATION``.
186-
Authorization schema is associated with ``_authz<SHEMA SHORT NAME>`` method and will be applied alternately as they are defined in the variable until one of them is successfully executed.
191+
It is worth noting that DIRAC supports several ways to authorize
192+
the request and they are all descriptive in ``DEFAULT_AUTHENTICATION``.
193+
Authorization schema is associated with ``_authz<SHEMA SHORT NAME>``
194+
method and will be applied alternately as they are defined in the variable until one of them is successfully executed.
187195
If no authorization method completes successfully, access will be denied.
188196
The following authorization schemas are supported by default:
189197
@@ -250,7 +258,8 @@ def _authzMYAUTH(self):
250258
- authentication request using one of the available algorithms called ``DEFAULT_AUTHENTICATION``, see :py:meth:`_gatherPeerCredentials` for more details.
251259
- and finally authorizing the request to access the component, see :py:meth:`authQuery <DIRAC.Core.DISET.AuthManager.AuthManager.authQuery>` for more details.
252260
253-
If all goes well, then a method is executed, the name of which coincides with the name of the request method (e.g.: :py:meth:`get`) which does:
261+
If all goes well, then a method is executed,
262+
the name of which coincides with the name of the request method (e.g.: :py:meth:`get`) which does:
254263
255264
- execute the target method in an executor a separate thread.
256265
- defines the arguments of the target method, see :py:meth:`_getMethodArgs`.
@@ -279,7 +288,8 @@ def _authzMYAUTH(self):
279288
# Below are variables that the developer can OVERWRITE as needed
280289

281290
# System name with which this component is associated.
282-
# Developer can overwrite this if your handler is outside the DIRAC system package (src/DIRAC/XXXSystem/<path to your handler>)
291+
# Developer can overwrite this
292+
# if your handler is outside the DIRAC system package (src/DIRAC/XXXSystem/<path to your handler>)
283293
SYSTEM_NAME = None
284294
COMPONENT_NAME = None
285295

@@ -295,7 +305,9 @@ def _authzMYAUTH(self):
295305
# Prefix of the target methods names if need to use a special prefix. By default its "export_".
296306
METHOD_PREFIX = "export_"
297307

298-
# What authorization type to use. This definition refers to the type of authentication, ie which algorithm will be used to verify the incoming request and obtain user credentials.
308+
# What authorization type to use.
309+
# This definition refers to the type of authentication,
310+
# ie which algorithm will be used to verify the incoming request and obtain user credentials.
299311
# These algorithms will be applied in the same order as in the list.
300312
# SSL - add to list to enable certificate reading
301313
# JWT - add to list to enable reading Bearer token
@@ -697,7 +709,8 @@ def _gatherPeerCredentials(self, grants: list = None) -> dict:
697709
- reading Bearer token, see :py:meth`_authzJWT`.
698710
- authentication as visitor, that is, without verification, see :py:meth`_authzVISITOR`.
699711
700-
To add your own authentication type, create a `_authzYourGrantType` method that should return ``S_OK(dict)`` in case of successful authorization.
712+
To add your own authentication type, create a `_authzYourGrantType` method that should return ``S_OK(dict)``
713+
in case of successful authorization.
701714
702715
:param grants: grants to use
703716
@@ -724,7 +737,7 @@ def _gatherPeerCredentials(self, grants: list = None) -> dict:
724737
raise Exception("; ".join(err))
725738

726739
def _authzSSL(self):
727-
"""Load client certchain in DIRAC and extract informations.
740+
"""Load client certchain in DIRAC and extract information.
728741
729742
:return: S_OK(dict)/S_ERROR()
730743
"""
@@ -755,6 +768,22 @@ def _authzSSL(self):
755768

756769
credDict = res["Value"]
757770

771+
credDict["x509Chain"] = peerChain
772+
res = peerChain.isProxy()
773+
if not res["OK"]:
774+
return res
775+
credDict["isProxy"] = res["Value"]
776+
777+
if credDict["isProxy"]:
778+
credDict["DN"] = credDict["identity"]
779+
else:
780+
credDict["DN"] = credDict["subject"]
781+
782+
res = peerChain.isLimitedProxy()
783+
if not res["OK"]:
784+
return res
785+
credDict["isLimitedProxy"] = res["Value"]
786+
758787
# We check if client sends extra credentials...
759788
if "extraCredentials" in self.request.arguments:
760789
extraCred = self.get_argument("extraCredentials")
@@ -763,7 +792,7 @@ def _authzSSL(self):
763792
return S_OK(credDict)
764793

765794
def _authzJWT(self, accessToken=None):
766-
"""Load token claims in DIRAC and extract informations.
795+
"""Load token claims in DIRAC and extract information.
767796
768797
:param str accessToken: access_token
769798

src/DIRAC/FrameworkSystem/Agent/MyProxyRenewalAgent.py

Lines changed: 0 additions & 69 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
""" ProxyRenewalAgent keeps the proxy repository clean.
2+
3+
.. literalinclude:: ../ConfigTemplate.cfg
4+
:start-after: ##BEGIN ProxyRenewalAgent
5+
:end-before: ##END
6+
:dedent: 2
7+
:caption: ProxyRenewalAgent options
8+
"""
9+
import concurrent.futures
10+
11+
from DIRAC import S_OK
12+
13+
from DIRAC.Core.Base.AgentModule import AgentModule
14+
from DIRAC.FrameworkSystem.DB.ProxyDB import ProxyDB
15+
16+
DEFAULT_MAIL_FROM = "[email protected]"
17+
18+
19+
class ProxyRenewalAgent(AgentModule):
20+
def initialize(self):
21+
22+
requiredLifeTime = self.am_getOption("MinimumLifeTime", 3600)
23+
renewedLifeTime = self.am_getOption("RenewedLifeTime", 54000)
24+
mailFrom = self.am_getOption("MailFrom", DEFAULT_MAIL_FROM)
25+
self.useMyProxy = self.am_getOption("UseMyProxy", False)
26+
self.proxyDB = ProxyDB(useMyProxy=self.useMyProxy, mailFrom=mailFrom)
27+
28+
self.log.info(f"Minimum Life time : {requiredLifeTime}")
29+
self.log.info(f"Life time on renew : {renewedLifeTime}")
30+
if self.useMyProxy:
31+
self.log.info(f"MyProxy server : {self.proxyDB.getMyProxyServer()}")
32+
self.log.info(f"MyProxy max proxy time : {self.proxyDB.getMyProxyMaxLifeTime()}")
33+
34+
return S_OK()
35+
36+
def __renewProxyForCredentials(self, userDN, userGroup):
37+
lifeTime = self.am_getOption("RenewedLifeTime", 54000)
38+
self.log.info(f"Renewing for {userDN}@{userGroup} {lifeTime} secs")
39+
res = self.proxyDB.renewFromMyProxy(userDN, userGroup, lifeTime=lifeTime)
40+
if not res["OK"]:
41+
self.log.error("Failed to renew proxy", f"for {userDN}@{userGroup} : {res['Message']}")
42+
else:
43+
self.log.info(f"Renewed proxy for {userDN}@{userGroup}")
44+
45+
def execute(self):
46+
"""The main agent execution method"""
47+
self.log.verbose("Purging expired requests")
48+
res = self.proxyDB.purgeExpiredRequests()
49+
if not res["OK"]:
50+
self.log.error(res["Message"])
51+
else:
52+
self.log.info(f"Purged {res['Value']} requests")
53+
54+
self.log.verbose("Purging expired tokens")
55+
res = self.proxyDB.purgeExpiredTokens()
56+
if not res["OK"]:
57+
self.log.error(res["Message"])
58+
else:
59+
self.log.info(f"Purged {res['Value']} tokens")
60+
61+
self.log.verbose("Purging expired proxies")
62+
res = self.proxyDB.purgeExpiredProxies()
63+
if not res["OK"]:
64+
self.log.error(res["Message"])
65+
else:
66+
self.log.info(f"Purged {res['Value']} proxies")
67+
68+
self.log.verbose("Purging logs")
69+
res = self.proxyDB.purgeLogs()
70+
if not res["OK"]:
71+
self.log.error(res["Message"])
72+
73+
if self.useMyProxy:
74+
res = self.proxyDB.getCredentialsAboutToExpire(self.am_getOption("MinimumLifeTime", 3600))
75+
if not res["OK"]:
76+
return res
77+
data = res["Value"]
78+
self.log.info(f"Renewing {len(data)} proxies...")
79+
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
80+
futures = []
81+
for record in data:
82+
userDN = record[0]
83+
userGroup = record[1]
84+
futures.append(executor.submit(self.__renewProxyForCredentials, userDN, userGroup))
85+
return S_OK()

0 commit comments

Comments
 (0)