Changing session cookie name and added a way for clients to know what the name is #11413 (#11679)

* Changing session cookie name and added a way for clients to know what the key name is
* Adding session information to docs
* Fixing how awxkit gets the session id header
This commit is contained in:
John Westcott IV
2022-02-27 07:27:25 -05:00
committed by GitHub
parent 895c05a84a
commit cb57752903
7 changed files with 39 additions and 17 deletions

View File

@@ -99,6 +99,7 @@ class LoggedLoginView(auth_views.LoginView):
current_user = smart_text(JSONRenderer().render(current_user.data)) current_user = smart_text(JSONRenderer().render(current_user.data))
current_user = urllib.parse.quote('%s' % current_user, '') current_user = urllib.parse.quote('%s' % current_user, '')
ret.set_cookie('current_user', current_user, secure=settings.SESSION_COOKIE_SECURE or None) ret.set_cookie('current_user', current_user, secure=settings.SESSION_COOKIE_SECURE or None)
ret.setdefault('X-API-Session-Cookie-Name', getattr(settings, 'SESSION_COOKIE_NAME', 'awx_sessionid'))
return ret return ret
else: else:

View File

@@ -252,6 +252,10 @@ SESSION_COOKIE_SECURE = True
# Note: This setting may be overridden by database settings. # Note: This setting may be overridden by database settings.
SESSION_COOKIE_AGE = 1800 SESSION_COOKIE_AGE = 1800
# Name of the cookie that contains the session information.
# Note: Changing this value may require changes to any clients.
SESSION_COOKIE_NAME = 'awx_sessionid'
# Maximum number of per-user valid, concurrent sessions. # Maximum number of per-user valid, concurrent sessions.
# -1 is unlimited # -1 is unlimited
# Note: This setting may be overridden by database settings. # Note: This setting may be overridden by database settings.

View File

@@ -46,6 +46,7 @@ class CompleteView(BaseRedirectView):
current_user = smart_text(JSONRenderer().render(current_user.data)) current_user = smart_text(JSONRenderer().render(current_user.data))
current_user = urllib.parse.quote('%s' % current_user, '') current_user = urllib.parse.quote('%s' % current_user, '')
response.set_cookie('current_user', current_user, secure=settings.SESSION_COOKIE_SECURE or None) response.set_cookie('current_user', current_user, secure=settings.SESSION_COOKIE_SECURE or None)
response.setdefault('X-API-Session-Cookie-Name', getattr(settings, 'SESSION_COOKIE_NAME', 'awx_sessionid'))
return response return response

View File

@@ -33,6 +33,10 @@ class Connection(object):
def __init__(self, server, verify=False): def __init__(self, server, verify=False):
self.server = server self.server = server
self.verify = verify self.verify = verify
# Note: We use the old sessionid here incase someone is trying to connect to an older AWX version
# There is a check below so that if AWX returns an X-API-Session-Cookie-Name we will grab it and
# connect with the new session cookie name.
self.session_cookie_name = 'sessionid'
if not self.verify: if not self.verify:
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
@@ -49,8 +53,13 @@ class Connection(object):
_next = kwargs.get('next') _next = kwargs.get('next')
if _next: if _next:
headers = self.session.headers.copy() headers = self.session.headers.copy()
self.post('/api/login/', headers=headers, data=dict(username=username, password=password, next=_next)) response = self.post('/api/login/', headers=headers, data=dict(username=username, password=password, next=_next))
self.session_id = self.session.cookies.get('sessionid') # The login causes a redirect so we need to search the history of the request to find the header
for historical_response in response.history:
if 'X-API-Session-Cookie-Name' in historical_response.headers:
self.session_cookie_name = historical_response.headers.get('X-API-Session-Cookie-Name')
self.session_id = self.session.cookies.get(self.session_cookie_name, None)
self.uses_session_cookie = True self.uses_session_cookie = True
else: else:
self.session.auth = (username, password) self.session.auth = (username, password)
@@ -61,7 +70,7 @@ class Connection(object):
def logout(self): def logout(self):
if self.uses_session_cookie: if self.uses_session_cookie:
self.session.cookies.pop('sessionid', None) self.session.cookies.pop(self.session_cookie_name, None)
else: else:
self.session.auth = None self.session.auth = None

View File

@@ -95,12 +95,12 @@ def as_user(v, username, password=None):
# requests doesn't provide interface for retrieving # requests doesn't provide interface for retrieving
# domain segregated cookies other than iterating. # domain segregated cookies other than iterating.
for cookie in connection.session.cookies: for cookie in connection.session.cookies:
if cookie.name == 'sessionid': if cookie.name == connection.session_cookie_name:
session_id = cookie.value session_id = cookie.value
domain = cookie.domain domain = cookie.domain
break break
if session_id: if session_id:
del connection.session.cookies['sessionid'] del connection.session.cookies[connection.session_cookie_name]
if access_token: if access_token:
kwargs = dict(token=access_token) kwargs = dict(token=access_token)
else: else:
@@ -114,9 +114,9 @@ def as_user(v, username, password=None):
if config.use_sessions: if config.use_sessions:
if access_token: if access_token:
connection.session.auth = None connection.session.auth = None
del connection.session.cookies['sessionid'] del connection.session.cookies[connection.session_cookie_name]
if session_id: if session_id:
connection.session.cookies.set('sessionid', session_id, domain=domain) connection.session.cookies.set(connection.session_cookie_name, session_id, domain=domain)
else: else:
connection.session.auth = previous_auth connection.session.auth = previous_auth

View File

@@ -51,7 +51,9 @@ class WSClient(object):
# Subscription group types # Subscription group types
def __init__(self, token=None, hostname='', port=443, secure=True, session_id=None, csrftoken=None, add_received_time=False): def __init__(
self, token=None, hostname='', port=443, secure=True, session_id=None, csrftoken=None, add_received_time=False, session_cookie_name='awx_sessionid'
):
# delay this import, because this is an optional dependency # delay this import, because this is an optional dependency
import websocket import websocket
@@ -78,7 +80,7 @@ class WSClient(object):
if self.token is not None: if self.token is not None:
auth_cookie = 'token="{0.token}";'.format(self) auth_cookie = 'token="{0.token}";'.format(self)
elif self.session_id is not None: elif self.session_id is not None:
auth_cookie = 'sessionid="{0.session_id}"'.format(self) auth_cookie = '{1}="{0.session_id}"'.format(self, session_cookie_name)
if self.csrftoken: if self.csrftoken:
auth_cookie += ';csrftoken={0.csrftoken}'.format(self) auth_cookie += ';csrftoken={0.csrftoken}'.format(self)
else: else:

View File

@@ -6,9 +6,9 @@ Session authentication is a safer way of utilizing HTTP(S) cookies. Theoreticall
`Cookie` header, but this method is vulnerable to cookie hijacks, where crackers can see and steal user `Cookie` header, but this method is vulnerable to cookie hijacks, where crackers can see and steal user
information from the cookie payload. information from the cookie payload.
Session authentication, on the other hand, sets a single `session_id` cookie. The `session_id` Session authentication, on the other hand, sets a single `awx_sessionid` cookie. The `awx_sessionid`
is *a random string which will be mapped to user authentication informations by server*. Crackers who is *a random string which will be mapped to user authentication information by the server*. Crackers who
hijack cookies will only get the `session_id` itself, which does not imply any critical user info, is valid only for hijack cookies will only get the `awx_sessionid` itself, which does not imply any critical user info, is valid only for
a limited time, and can be revoked at any time. a limited time, and can be revoked at any time.
> Note: The CSRF token will by default allow HTTP. To increase security, the `CSRF_COOKIE_SECURE` setting should > Note: The CSRF token will by default allow HTTP. To increase security, the `CSRF_COOKIE_SECURE` setting should
@@ -34,22 +34,27 @@ be provided in the form:
* `next`: The path of the redirect destination, in API browser `"/api/"` is used. * `next`: The path of the redirect destination, in API browser `"/api/"` is used.
* `csrfmiddlewaretoken`: The CSRF token, usually populated by using Django template `{% csrf_token %}`. * `csrfmiddlewaretoken`: The CSRF token, usually populated by using Django template `{% csrf_token %}`.
The `session_id` is provided as a return `Set-Cookie` header. Here is a typical one: The `awx_session_id` is provided as a return `Set-Cookie` header. Here is a typical one:
``` ```
Set-Cookie: sessionid=lwan8l5ynhrqvps280rg5upp7n3yp6ds; expires=Tue, 21-Nov-2017 16:33:13 GMT; httponly; Max-Age=1209600; Path=/ Set-Cookie: awx_sessionid=lwan8l5ynhrqvps280rg5upp7n3yp6ds; expires=Tue, 21-Nov-2017 16:33:13 GMT; httponly; Max-Age=1209600; Path=/
``` ```
In addition, when the `awx_sessionid` a header called `X-API-Session-Cookie-Name` this header will only be displayed once on a successful logging and denotes the name of the session cookie name. By default this is `awx_sessionid` but can be changed (see below).
Any client should follow the standard rules of [cookie protocol](https://tools.ietf.org/html/rfc6265) to Any client should follow the standard rules of [cookie protocol](https://tools.ietf.org/html/rfc6265) to
parse that header to obtain information about the session, such as session cookie name (`session_id`), parse that header to obtain information about the session, such as session cookie name (`awx_sessionid`),
session cookie value, expiration date, duration, etc. session cookie value, expiration date, duration, etc.
The name of the cookie is configurable by Tower Configuration setting `SESSION_COOKIE_NAME` under the category `authentication`. It is a string. The default session cookie name is `awx_sessionid`.
The duration of the cookie is configurable by Tower Configuration setting `SESSION_COOKIE_AGE` under The duration of the cookie is configurable by Tower Configuration setting `SESSION_COOKIE_AGE` under
category `authentication`. It is an integer denoting the number of seconds the session cookie should category `authentication`. It is an integer denoting the number of seconds the session cookie should
live. The default session cookie age is two weeks. live. The default session cookie age is two weeks.
After a valid session is acquired, a client should provide the `session_id` as a cookie for subsequent requests After a valid session is acquired, a client should provide the `awx_sessionid` as a cookie for subsequent requests
in order to be authenticated. For example: in order to be authenticated. For example:
``` ```
Cookie: sessionid=lwan8l5ynhrqvps280rg5upp7n3yp6ds; ... Cookie: awx_sessionid=lwan8l5ynhrqvps280rg5upp7n3yp6ds; ...
``` ```
User should use the `/api/logout/` endpoint to log out. In the API browser, a logged-in user can do that by User should use the `/api/logout/` endpoint to log out. In the API browser, a logged-in user can do that by