]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/services/auth.py
1 # -*- coding: utf-8 -*-
9 from base64
import b64encode
15 from .access_control
import LocalAuthenticator
, UserDoesNotExist
17 cherrypy
.config
.update({
18 'response.headers.server': 'Ceph-Dashboard',
19 'response.headers.content-security-policy': "frame-ancestors 'self';",
20 'response.headers.x-content-type-options': 'nosniff',
21 'response.headers.strict-transport-security': 'max-age=63072000; includeSubDomains; preload'
25 class JwtManager(object):
26 JWT_TOKEN_BLOCKLIST_KEY
= "jwt_token_block_list"
27 JWT_TOKEN_TTL
= 28800 # default 8 hours
28 JWT_ALGORITHM
= 'HS256'
31 LOCAL_USER
= threading
.local()
35 secret
= os
.urandom(16)
36 return b64encode(secret
).decode('utf-8')
40 cls
.logger
= logging
.getLogger('jwt') # type: ignore
41 # generate a new secret if it does not exist
42 secret
= mgr
.get_store('jwt_secret')
44 secret
= cls
._gen
_secret
()
45 mgr
.set_store('jwt_secret', secret
)
49 def gen_token(cls
, username
):
52 ttl
= mgr
.get_module_option('jwt_token_ttl', cls
.JWT_TOKEN_TTL
)
54 now
= int(time
.time())
56 'iss': 'ceph-dashboard',
57 'jti': str(uuid
.uuid4()),
62 return jwt
.encode(payload
, cls
._secret
, algorithm
=cls
.JWT_ALGORITHM
) # type: ignore
65 def decode_token(cls
, token
):
68 return jwt
.decode(token
, cls
._secret
, algorithms
=cls
.JWT_ALGORITHM
) # type: ignore
71 def get_token_from_header(cls
):
72 auth_cookie_name
= 'token'
75 return cherrypy
.request
.cookie
[auth_cookie_name
].value
78 # fall-back: use Authorization header
79 auth_header
= cherrypy
.request
.headers
.get('authorization')
80 if auth_header
is not None:
81 scheme
, params
= auth_header
.split(' ', 1)
82 if scheme
.lower() == 'bearer':
88 def set_user(cls
, username
):
89 cls
.LOCAL_USER
.username
= username
96 def get_username(cls
):
97 return getattr(cls
.LOCAL_USER
, 'username', None)
100 def get_user(cls
, token
):
102 dtoken
= JwtManager
.decode_token(token
)
103 if not JwtManager
.is_blocklisted(dtoken
['jti']):
104 user
= AuthManager
.get_user(dtoken
['username'])
105 if user
.last_update
<= dtoken
['iat']:
107 cls
.logger
.debug( # type: ignore
108 "user info changed after token was issued, iat=%s last_update=%s",
109 dtoken
['iat'], user
.last_update
112 cls
.logger
.debug('Token is block-listed') # type: ignore
113 except jwt
.ExpiredSignatureError
:
114 cls
.logger
.debug("Token has expired") # type: ignore
115 except jwt
.InvalidTokenError
:
116 cls
.logger
.debug("Failed to decode token") # type: ignore
117 except UserDoesNotExist
:
118 cls
.logger
.debug( # type: ignore
119 "Invalid token: user %s does not exist", dtoken
['username']
124 def blocklist_token(cls
, token
):
125 token
= cls
.decode_token(token
)
126 blocklist_json
= mgr
.get_store(cls
.JWT_TOKEN_BLOCKLIST_KEY
)
127 if not blocklist_json
:
128 blocklist_json
= "{}"
129 bl_dict
= json
.loads(blocklist_json
)
132 # remove expired tokens
134 for jti
, exp
in bl_dict
.items():
136 to_delete
.append(jti
)
137 for jti
in to_delete
:
140 bl_dict
[token
['jti']] = token
['exp']
141 mgr
.set_store(cls
.JWT_TOKEN_BLOCKLIST_KEY
, json
.dumps(bl_dict
))
144 def is_blocklisted(cls
, jti
):
145 blocklist_json
= mgr
.get_store(cls
.JWT_TOKEN_BLOCKLIST_KEY
)
146 if not blocklist_json
:
147 blocklist_json
= "{}"
148 bl_dict
= json
.loads(blocklist_json
)
149 return jti
in bl_dict
152 class AuthManager(object):
157 cls
.AUTH_PROVIDER
= LocalAuthenticator()
160 def get_user(cls
, username
):
161 return cls
.AUTH_PROVIDER
.get_user(username
) # type: ignore
164 def authenticate(cls
, username
, password
):
165 return cls
.AUTH_PROVIDER
.authenticate(username
, password
) # type: ignore
168 def authorize(cls
, username
, scope
, permissions
):
169 return cls
.AUTH_PROVIDER
.authorize(username
, scope
, permissions
) # type: ignore
172 class AuthManagerTool(cherrypy
.Tool
):
174 super(AuthManagerTool
, self
).__init
__(
175 'before_handler', self
._check
_authentication
, priority
=20)
176 self
.logger
= logging
.getLogger('auth')
178 def _check_authentication(self
):
179 JwtManager
.reset_user()
180 token
= JwtManager
.get_token_from_header()
182 user
= JwtManager
.get_user(token
)
184 self
._check
_authorization
(user
.username
)
186 self
.logger
.debug('Unauthorized access to %s',
187 cherrypy
.url(relative
='server'))
188 raise cherrypy
.HTTPError(401, 'You are not authorized to access '
191 def _check_authorization(self
, username
):
192 self
.logger
.debug("checking authorization...")
193 handler
= cherrypy
.request
.handler
.callable
194 controller
= handler
.__self
__
195 sec_scope
= getattr(controller
, '_security_scope', None)
196 sec_perms
= getattr(handler
, '_security_permissions', None)
197 JwtManager
.set_user(username
)
200 # controller does not define any authorization restrictions
203 self
.logger
.debug("checking '%s' access to '%s' scope", sec_perms
,
207 self
.logger
.debug("Fail to check permission on: %s:%s", controller
,
209 raise cherrypy
.HTTPError(403, "You don't have permissions to "
210 "access that resource")
212 if not AuthManager
.authorize(username
, sec_scope
, sec_perms
):
213 raise cherrypy
.HTTPError(403, "You don't have permissions to "
214 "access that resource")