Skip to content
This repository has been archived by the owner on Jul 15, 2021. It is now read-only.

Commit

Permalink
Merge pull request #7 from apakulov-stripe/update-to-5.2.0
Browse files Browse the repository at this point in the history
Update to 5.2.0
  • Loading branch information
apakulov-stripe authored Oct 30, 2019
2 parents cceaa80 + c8cbc2b commit d43f0a6
Show file tree
Hide file tree
Showing 30 changed files with 257 additions and 850 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dist: bionic
language: python
env:
- REPO=lyft/confidant
Expand All @@ -8,7 +9,7 @@ before_install:
- docker build -f Dockerfile -t $REPO:$TRAVIS_COMMIT .
- docker run -v $PWD/confidant/dist:/tmp/dist $REPO:$TRAVIS_COMMIT /bin/sh -c "cp -r /srv/confidant/confidant/dist/. /tmp/dist/."
install:
- gem install travis --no-rdoc --no-ri
- gem install travis --no-document
script:
- travis lint .travis.yml --skip-completion-check
- docker run $REPO:$TRAVIS_COMMIT /bin/sh -c "make test"
Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
# Changelog

## 5.2.0

* Python3 fix in function ``load_private_key_pem`` in ``confidant.lib.cryptolib``

## 5.1.0

* Python3 fix in class ``CipherManager`` in ``confidant.ciphermanager``

## 5.0.1

* Packaging fixes for docker

## 5.0.0

* This is a breaking release. This release slightly changes the values needed
for the ``AUTH_KEY``, ``USER_AUTH_KEY``, and ``KMS_MASTER_KEY`` settings.
The previous way these settings were set were to use the alias name, without
an ``alias/`` prefix. In this release we switched to using the kmsauth
library for kms authentication support, which supports aliases and ARNs for
keys, which means that for these three settings, it's necessary to add an
``alias/`` prefix to the value. So, for example, if your setting was
``my-auth-key``, the new value would be ``alias/my-auth-key``. Though this
change of behavior was limited to kmsauth, for consistency we also changed
``KMS_MASTER_KEY`` to use the same behavior. For all three settings, it's
also now possible to use ARNs as values, instead of just key aliases.
* confidant now supports python2 and python3.
* Requirements have been updated to resolve some reported security
vulnerabilities in a few of the frozen requirements. A library affecting
user sessions was upgraded which will cause users to be logged out after
upgrade, which means if you're doing a rolling upgrade, that during the
upgrade, you may have users that seemingly randomly get logged out. After
a finished upgrade, users should only be logged out once, if they're
currently logged in.

## 4.4.0

* Use ``dict`` and ``set`` in pynamo models rather than ``{}`` and ``set()``,
Expand Down
19 changes: 8 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ubuntu:trusty
FROM ubuntu:bionic
LABEL maintainer="[email protected]"

RUN apt-get update \
Expand All @@ -11,24 +11,21 @@ RUN apt-get update \
make ruby-dev nodejs git-core \
# For backend
gcc pkg-config \
python-dev python-virtualenv \
python3-dev virtualenv \
libffi-dev libxml2-dev libxmlsec1-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

COPY ./piptools_requirements.txt /srv/confidant/piptools_requirements.txt
COPY ./requirements.txt /srv/confidant/requirements.txt
COPY ./package.json /srv/confidant/package.json
COPY ./bower.json /srv/confidant/bower.json
COPY package.json bower.json piptools_requirements*.txt requirements*.txt /srv/confidant/

WORKDIR /srv/confidant

ENV PATH=/venv/bin:$PATH
RUN virtualenv /venv && \
pip install --upgrade pip && \
pip install -r piptools_requirements.txt && \
pip install -r requirements.txt
RUN virtualenv /venv -ppython3 && \
pip install --no-cache -r piptools_requirements3.txt && \
pip install --no-cache -r requirements3.txt

RUN gem install rb-inotify -v 0.9.10 && \
RUN gem install ffi -v 1.10.0 && \
gem install rb-inotify -v 0.9.10 && \
gem install compass -v 1.0.3 && \
npm install grunt-cli && \
npm install
Expand Down
99 changes: 49 additions & 50 deletions confidant/authnz/__init__.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
import fnmatch
import logging

import kmsauth
from flask import abort, request, g, make_response
from flask import url_for
from functools import wraps

from confidant import keymanager
from confidant.app import app
from confidant.utils import stats

from confidant.authnz.errors import (UserUnknownError, TokenVersionError,
AuthenticationError, NotAuthorized)
from confidant.authnz.errors import (
UserUnknownError,
AuthenticationError,
NotAuthorized,
)
from confidant.authnz import userauth

PRIVILEGES = {
'user': ['*'],
'service': ['get_service']
}
_VALIDATOR = None

user_mod = userauth.init_user_auth_class()


def _get_validator():
global _VALIDATOR
if _VALIDATOR is None:
_VALIDATOR = kmsauth.KMSTokenValidator(
app.config['AUTH_KEY'],
app.config['USER_AUTH_KEY'],
app.config['AUTH_CONTEXT'],
app.config['AWS_DEFAULT_REGION'],
auth_token_max_lifetime=app.config['AUTH_TOKEN_MAX_LIFETIME'],
minimum_token_version=app.config['KMS_MINIMUM_TOKEN_VERSION'],
maximum_token_version=app.config['KMS_MAXIMUM_TOKEN_VERSION'],
scoped_auth_keys=app.config['SCOPED_AUTH_KEYS'],
token_cache_size=app.config['KMS_AUTH_TOKEN_CACHE_SIZE'],
stats=stats,
)
return _VALIDATOR


def get_logged_in_user():
'''
Retrieve logged-in user's email that is stored in cache
Expand Down Expand Up @@ -84,42 +106,21 @@ def decorated(*args, **kwargs):
return decorated


def _parse_username(username):
username_arr = username.split('/')
if len(username_arr) == 3:
# V2 token format: version/service/myservice or version/user/myuser
version = int(username_arr[0])
user_type = username_arr[1]
username = username_arr[2]
elif len(username_arr) == 1:
# Old format, specific to services: myservice
version = 1
username = username_arr[0]
user_type = 'service'
else:
raise TokenVersionError('Unsupported username format.')
return version, user_type, username


def _get_kms_auth_data():
data = {}
auth = request.authorization
headers = request.headers
if auth and auth.get('username'):
if not auth.get('password'):
raise AuthenticationError('No password provided via basic auth.')
(data['version'],
data['user_type'],
data['from']) = _parse_username(auth['username'])
data['username'] = auth['username']
data['token'] = auth['password']
elif 'X-Auth-Token' in headers and 'X-Auth-From' in headers:
if not headers.get('X-Auth-Token'):
raise AuthenticationError(
'No X-Auth-Token provided via auth headers.'
)
(data['version'],
data['user_type'],
data['from']) = _parse_username(headers['X-Auth-From'])
data['username'] = headers['X-Auth-From']
data['token'] = headers['X-Auth-Token']
return data

Expand Down Expand Up @@ -154,59 +155,57 @@ def decorated(*args, **kwargs):
# User suppplied basic auth info
try:
kms_auth_data = _get_kms_auth_data()
except TokenVersionError:
logging.warning('Invalid token version used.')
return abort(403)
except AuthenticationError:
logging.exception('Failed to authenticate request.')
return abort(403)
if kms_auth_data:
validator = _get_validator()
_from = validator.extract_username_field(
kms_auth_data['username'],
'from',
)
_user_type = validator.extract_username_field(
kms_auth_data['username'],
'user_type',
)
try:
if (kms_auth_data['user_type']
not in app.config['KMS_AUTH_USER_TYPES']):
if _user_type not in app.config['KMS_AUTH_USER_TYPES']:
msg = '{0} is not an allowed user type for KMS auth.'
msg = msg.format(kms_auth_data['user_type'])
msg = msg.format(_user_type)
logging.warning(msg)
return abort(403)
with stats.timer('decrypt_token'):
token_data = keymanager.decrypt_token(
kms_auth_data['version'],
kms_auth_data['user_type'],
kms_auth_data['from'],
token_data = validator.decrypt_token(
kms_auth_data['username'],
kms_auth_data['token']
)
logging.debug('Auth request had the following token_data:'
' {0}'.format(token_data))
msg = 'Authenticated {0} with user_type {1} via kms auth'
msg = msg.format(
kms_auth_data['from'],
kms_auth_data['user_type']
)
msg = msg.format(_from, _user_type)
logging.debug(msg)
if user_type_has_privilege(
kms_auth_data['user_type'],
f.func_name):
g.user_type = kms_auth_data['user_type']
if user_type_has_privilege(_user_type, f.__name__):
g.user_type = _user_type
g.auth_type = 'kms'
g.account = account_for_key_alias(token_data['key_alias'])
g.username = kms_auth_data['from']
g.username = _from
return f(*args, **kwargs)
else:
msg = '{0} is not authorized to access {1}.'
msg = msg.format(kms_auth_data['from'], f.func_name)
msg = msg.format(_from, f.__name__)
logging.warning(msg)
return abort(403)
except keymanager.TokenDecryptionError:
except kmsauth.TokenValidationError:
logging.exception('Failed to decrypt authentication token.')
msg = 'Access denied for {0}. Authentication Failed.'
msg = msg.format(kms_auth_data['from'])
msg = msg.format(_from)
logging.warning(msg)
return abort(403)

# If not using kms auth, require auth using the user_mod authn module.
else:
user_type = 'user'
if not user_type_has_privilege(user_type, f.func_name):
if not user_type_has_privilege(user_type, f.__name__):
return abort(403)

if user_mod.is_expired():
Expand All @@ -216,7 +215,7 @@ def decorated(*args, **kwargs):
try:
user_mod.check_authorization()
except NotAuthorized as e:
logging.warning('Not authorized -- ' + e.message)
logging.warning('Not authorized -- {}'.format(e))
return abort(403)
else:
# User took an action, extend the expiration time.
Expand Down
34 changes: 9 additions & 25 deletions confidant/authnz/userauth.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import abc
import logging
import urlparse
import datetime
import random

import yaml
from six.moves.urllib.parse import urlparse

import flask
from flask import request, session
Expand Down Expand Up @@ -121,7 +121,9 @@ def set_current_user(self, email, first_name=None, last_name=None):
}

def current_email(self):
return self.current_user()['email'].lower()
ret = self.current_user()['email'].lower()
# when migrating from 2 -> 3, the session email object may be bytes
return ret.decode('UTF-8') if isinstance(ret, bytes) else ret

def current_first_name(self):
return self.current_user()['first_name']
Expand Down Expand Up @@ -231,7 +233,7 @@ def passes_email_whitelist(self, email):
return True


class NullUserAuthenticator(object):
class NullUserAuthenticator(AbstractUserAuthenticator):
"""
Fake user authenticator class that performs no authentication.
"""
Expand All @@ -251,15 +253,6 @@ def current_user(self):
'last_name': 'user',
}

def current_email(self):
return self.current_user()['email'].lower()

def current_first_name(self):
return self.current_user()['first_name']

def current_last_name(self):
return self.current_user()['last_name']

def is_authenticated(self):
"""Null users are always authenticated"""
return True
Expand Down Expand Up @@ -323,15 +316,6 @@ def current_user(self):

return info

def current_email(self):
return self.current_user()['email'].lower()

def current_first_name(self):
return self.current_user()['first_name']

def current_last_name(self):
return self.current_user()['last_name']

def is_authenticated(self):
"""Any user that is able to make requests is authenticated"""
self.assert_headers()
Expand Down Expand Up @@ -395,7 +379,7 @@ def log_in(self):
if result:
if result.error:
msg = 'Google auth failed with error: {0}'
logging.error(msg.format(result.error.message))
logging.error(msg.format(result.error))
return abort(403)

# successful login
Expand Down Expand Up @@ -602,7 +586,7 @@ def consume_saml_assertion(self):
logging.info('SAML attributes: {!r}'.format(attributes))

# normalize attributes by flattening single-item arrays
for key, val in attributes.iteritems():
for key, val in attributes.items():
if isinstance(val, list) and len(val) == 1:
attributes[key] = val[0]

Expand All @@ -618,7 +602,7 @@ def consume_saml_assertion(self):
kwargs['email'] = attributes.get('email', nameid)

# use first_name, last_name if present
for key, val in attributes.iteritems():
for key, val in attributes.items():
if not getattr(key, 'lower', None):
logging.error('Bad list attr {!r}'.format({key: val}))
if key.lower() in ['firstname', 'first_name']:
Expand Down Expand Up @@ -735,7 +719,7 @@ def _saml_req_dict_from_request(self, flask_request=None):
if flask_request is None:
flask_request = flask.request

url_data = urlparse.urlparse(flask_request.url)
url_data = urlparse(flask_request.url)

if flask_request.scheme == 'https':
https = 'on'
Expand Down
6 changes: 4 additions & 2 deletions confidant/ciphermanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ def encrypt(self, raw):
logging.warning('Not using encryption in CipherManager.encrypt'
' If you are not running in a development or test'
' environment, this should not be happening!')
return 'DANGER_NOT_ENCRYPTED_{0}'.format(base64.b64encode(raw))
return 'DANGER_NOT_ENCRYPTED_{0}'.format(
base64.b64encode(raw.encode('UTF-8')).decode('UTF-8'),
)
if self.version == 2:
f = Fernet(self.key)
return f.encrypt(raw.encode('utf-8'))
return f.encrypt(raw.encode('utf-8')).decode('UTF-8')
else:
raise CipherManagerError('Bad cipher version')

Expand Down
Loading

0 comments on commit d43f0a6

Please sign in to comment.