def get_service(id): ''' Get service metadata and all credentials for this service. This endpoint allows basic authentication. ''' if authnz.user_in_role('service') and not authnz.user_is_service(id): log.warning('Authz failed for service {0}.'.format(id)) msg = 'Authenticated user is not authorized.' return jsonify({'error': msg}), 401 log.debug('Authz succeeded for service {0}.'.format(id)) try: service = Service.get(id) except Service.DoesNotExist: return jsonify({}), 404 if (service.data_type != 'service' and service.data_type != 'archive-service'): return jsonify({}), 404 try: credentials = _get_credentials(service.credentials) except KeyError: return jsonify({'error': 'Decryption error.'}), 500 return jsonify({ 'id': service.id, 'credentials': credentials, 'enabled': service.enabled, 'revision': service.revision, 'modified_date': service.modified_date, 'modified_by': service.modified_by })
def create_datakey(encryption_context): ''' Create a datakey from KMS. ''' # Disabled encryption is dangerous, so we don't use falsiness here. if app.config['USE_ENCRYPTION'] is False: log.warning('Creating a mock data key in keymanager.create_datakey.' ' If you are not running in a development or test' ' environment, this should not be happening!') return _create_mock_datakey() # Fernet key; from spec and cryptography implementation, but using # random from KMS, rather than os.urandom: # https://github.com/fernet/spec/blob/master/Spec.md#key-format # https://cryptography.io/en/latest/_modules/cryptography/fernet/#Fernet.generate_key key = base64.urlsafe_b64encode( kms.generate_random(NumberOfBytes=32)['Plaintext'] ) key_alias = app.config.get('KMS_MASTER_KEY') response = kms.encrypt( KeyId='alias/{0}'.format(key_alias), Plaintext=key, EncryptionContext=encryption_context ) return {'ciphertext': response['CiphertextBlob'], 'plaintext': key}
def send_event(services, msg): try: graphite_url = app.config.get('GRAPHITE_EVENT_URL') if not graphite_url: return username = app.config.get('GRAPHITE_USERNAME') password = app.config.get('GRAPHITE_PASSWORD') headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} prefixed_services = ['confidant-{0}'.format(service) for service in services] event = { 'what': 'confidant', 'tags': ','.join(['confidant'] + prefixed_services), 'data': msg } response = requests.post( graphite_url, auth=(username, password), headers=headers, data=json.dumps(event), timeout=1 ) if response.status_code != 200: msg = 'Post to graphite returned non-2000 status ({0}).' log.warning(msg.format(response.status_code)) except Exception as e: log.warning('Failed to post graphite event. {0}'.format(e))
def custom_modules(path): if not app.config['CUSTOM_FRONTEND_DIRECTORY']: return '', 200 try: return send_from_directory( os.path.join(app.config['CUSTOM_FRONTEND_DIRECTORY'], 'modules'), path) except NotFound: log.warning('Client requested missing custom module {0}.'.format(path)) return '', 200
def decrypt_token(token, _from): ''' Decrypt a token. ''' try: token_key = '{0}{1}'.format(hashlib.sha256(token).hexdigest(), _from) except Exception: raise TokenDecryptionError('Authentication error.') if token_key not in TOKENS: try: token = base64.b64decode(token) with stats.timer('kms_decrypt_token'): data = kms.decrypt( CiphertextBlob=token, EncryptionContext={ # This key is sent to us. 'to': app.config['AUTH_CONTEXT'], # From a service. 'from': _from }) # Decrypt doesn't take KeyId as an argument. We need to verify the # correct key was used to do the decryption. # Annoyingly, the KeyId from the data is actually an arn. key_arn = data['KeyId'] if key_arn != get_key_arn(app.config['AUTH_KEY']): raise TokenDecryptionError('Authentication error.') plaintext = data['Plaintext'] payload = json.loads(plaintext) # We don't care what exception is thrown. For paranoia's sake, fail # here. except Exception: log.exception('Failed to validate token.') raise TokenDecryptionError('Authentication error.') else: payload = TOKENS[token_key] time_format = "%Y%m%dT%H%M%SZ" now = datetime.datetime.utcnow() try: not_before = datetime.datetime.strptime(payload['not_before'], time_format) not_after = datetime.datetime.strptime(payload['not_after'], time_format) except Exception: log.exception( 'Failed to get not_before and not_after from token payload.') raise TokenDecryptionError('Authentication error.') delta = (not_after - not_before).seconds / 60 if delta > app.config['AUTH_TOKEN_MAX_LIFETIME']: log.warning('Token used which exceeds max token lifetime.') raise TokenDecryptionError('Authentication error.') if not (now >= not_before) and (now <= not_after): log.warning('Expired token used.') raise TokenDecryptionError('Authentication error.') TOKENS[token_key] = payload return payload
def custom_modules(path): if not app.config['CUSTOM_FRONTEND_DIRECTORY']: return '', 200 try: return send_from_directory( os.path.join(app.config['CUSTOM_FRONTEND_DIRECTORY'], 'modules'), path ) except NotFound: log.warning('Client requested missing custom module {0}.'.format(path)) return '', 200
def decrypt(self, enc): # Disabled encryption is dangerous, so we don't use falsiness here. if app.config['USE_ENCRYPTION'] is False: log.warning('Not using encryption in CipherManager.decrypt' ' If you are not running in a development or test' ' environment, this should not be happening!') return base64.b64decode(re.sub(r'^DANGER_NOT_ENCRYPTED_', '', enc)) if self.version == 2: f = Fernet(self.key) return f.decrypt(enc.encode('utf-8')) else: raise CipherManagerError('Bad cipher version')
def decrypt(self, enc): # Disabled encryption is dangerous, so we don't use falsiness here. if app.config['USE_ENCRYPTION'] is False: log.warning('Not using encryption in CipherManager.decrypt' ' If you are not running in a development or test' ' environment, this should not be happening!') return enc if self.version == 2: f = Fernet(self.key) return f.decrypt(enc.encode('utf-8')) else: raise CipherManagerError('Bad cipher version')
def encrypt(self, raw): # Disabled encryption is dangerous, so we don't use falsiness here. if app.config['USE_ENCRYPTION'] is False: log.warning('Not using encryption in CipherManager.encrypt' ' If you are not running in a development or test' ' environment, this should not be happening!') return raw if self.version == 2: f = Fernet(self.key) return f.encrypt(raw.encode('utf-8')) else: raise CipherManagerError('Bad cipher version')
def decrypt_key(data_key, encryption_context=None): ''' Decrypt a datakey. ''' # Disabled encryption is dangerous, so we don't use falsiness here. if app.config['USE_ENCRYPTION'] is False: log.warning('Decypting a mock data key in keymanager.decrypt_key.' ' If you are not running in a development or test' ' environment, this should not be happening!') return _decrypt_mock_datakey(data_key) sha = hashlib.sha256(data_key).hexdigest() if sha not in DATAKEYS: plaintext = kms.decrypt( CiphertextBlob=data_key, EncryptionContext=encryption_context)['Plaintext'] DATAKEYS[sha] = plaintext return DATAKEYS[sha]
def decrypt_key(data_key, encryption_context=None): ''' Decrypt a datakey. ''' # Disabled encryption is dangerous, so we don't use falsiness here. if app.config['USE_ENCRYPTION'] is False: log.warning('Decypting a mock data key in keymanager.decrypt_key.' ' If you are not running in a development or test' ' environment, this should not be happening!') return _decrypt_mock_datakey(data_key) sha = hashlib.sha256(data_key).hexdigest() if sha not in DATAKEYS: plaintext = kms.decrypt( CiphertextBlob=data_key, EncryptionContext=encryption_context )['Plaintext'] DATAKEYS[sha] = plaintext return DATAKEYS[sha]
def create_datakey(encryption_context): ''' Create a datakey from KMS. ''' # Disabled encryption is dangerous, so we don't use falsiness here. if app.config['USE_ENCRYPTION'] is False: log.warning('Creating a mock data key in keymanager.create_datakey.' ' If you are not running in a development or test' ' environment, this should not be happening!') return _create_mock_datakey() # Fernet key; from spec and cryptography implementation, but using # random from KMS, rather than os.urandom: # https://github.com/fernet/spec/blob/master/Spec.md#key-format # https://cryptography.io/en/latest/_modules/cryptography/fernet/#Fernet.generate_key key = base64.urlsafe_b64encode( kms.generate_random(NumberOfBytes=32)['Plaintext']) key_alias = app.config.get('KMS_MASTER_KEY') response = kms.encrypt(KeyId='alias/{0}'.format(key_alias), Plaintext=key, EncryptionContext=encryption_context) return {'ciphertext': response['CiphertextBlob'], 'plaintext': key}
def decrypt_token(token, _from): ''' Decrypt a token. ''' try: token_key = '{0}{1}'.format( hashlib.sha256(token).hexdigest(), _from ) except Exception: raise TokenDecryptionError('Authentication error.') if token_key not in TOKENS: try: token = base64.b64decode(token) with stats.timer('kms_decrypt_token'): data = kms.decrypt( CiphertextBlob=token, EncryptionContext={ # This key is sent to us. 'to': app.config['AUTH_CONTEXT'], # From a service. 'from': _from } ) # Decrypt doesn't take KeyId as an argument. We need to verify the # correct key was used to do the decryption. # Annoyingly, the KeyId from the data is actually an arn. key_arn = data['KeyId'] if key_arn != get_key_arn(app.config['AUTH_KEY']): raise TokenDecryptionError('Authentication error.') plaintext = data['Plaintext'] payload = json.loads(plaintext) # We don't care what exception is thrown. For paranoia's sake, fail # here. except Exception: log.exception('Failed to validate token.') raise TokenDecryptionError('Authentication error.') else: payload = TOKENS[token_key] time_format = "%Y%m%dT%H%M%SZ" now = datetime.datetime.utcnow() try: not_before = datetime.datetime.strptime( payload['not_before'], time_format ) not_after = datetime.datetime.strptime( payload['not_after'], time_format ) except Exception: log.exception( 'Failed to get not_before and not_after from token payload.' ) raise TokenDecryptionError('Authentication error.') delta = (not_after - not_before).seconds / 60 if delta > app.config['AUTH_TOKEN_MAX_LIFETIME']: log.warning('Token used which exceeds max token lifetime.') raise TokenDecryptionError('Authentication error.') if not (now >= not_before) and (now <= not_after): log.warning('Expired token used.') raise TokenDecryptionError('Authentication error.') TOKENS[token_key] = payload return payload
def decorated(*args, **kwargs): if not app.config.get('USE_AUTH'): return f(*args, **kwargs) auth = request.authorization headers = request.headers using_basic_kms_auth = (auth and auth.get('username') and auth.get('password') != '') using_kms_auth = ('X-Auth-Token' in headers and 'X-Auth-From' in headers) # User suppplied basic auth info if using_basic_kms_auth or using_kms_auth: if using_basic_kms_auth: _from = auth['username'] token = auth['password'] else: _from = headers['X-Auth-From'] token = headers['X-Auth-Token'] try: with stats.timer('decrypt_token'): payload = keymanager.decrypt_token(token, _from) log.debug('Auth request had the following payload:' ' {0}'.format(payload)) role = 'service' msg = 'Authenticated {0} with role {1} via kms auth' msg = msg.format(_from, role) log.debug(msg) if role_has_privilege(role, f.func_name): g.auth_role = role g.username = _from return f(*args, **kwargs) else: msg = '{0} is not authorized to access {1}.' msg = msg.format(_from, f.func_name) log.warning(msg) return abort(403) except keymanager.TokenDecryptionError: msg = 'Access denied for {0}. Authentication Failed.' msg = msg.format(_from) log.warning(msg) return abort(403) # If not using kms auth, require google auth. else: role = 'user' if not role_has_privilege(role, f.func_name): return abort(403) if 'email' in session.get('google_oauth2', []): if (app.config['USERS_FILE'] and get_logged_in_user_email() not in users): msg = 'User not authorized: {0}' log.warning(msg.format(get_logged_in_user_email())) return abort(403) else: g.auth_role = role return f(*args, **kwargs) response = make_response() if request.is_secure: secure_cookie = True else: secure_cookie = False result = _authomatic.login( WerkzeugAdapter(request, response), 'google', session=session, session_saver=lambda: app.save_session(session, response), secure_cookie=secure_cookie) if result: if result.error: msg = 'Google auth failed with error: {0}' log.error(msg.format(result.error.message)) return abort(403) if result.user: result.user.update() user = result.user email_suffix = app.config['GOOGLE_AUTH_EMAIL_SUFFIX'] if email_suffix and not user.email.endswith(email_suffix): return abort(403) session['google_oauth2'] = {} session['google_oauth2']['email'] = user.email session['google_oauth2']['first_name'] = user.first_name session['google_oauth2']['last_name'] = user.last_name g.auth_role = role # TODO: find a way to save the angular args # authomatic adds url params google auth has stripped the # angular args anyway, so let's just redirect back to the # index. return redirect(url_for('index')) return response return abort(403)
def decorated(*args, **kwargs): if not app.config.get("USE_AUTH"): return f(*args, **kwargs) auth = request.authorization headers = request.headers using_basic_kms_auth = auth and auth.get("username") and auth.get("password") != "" using_kms_auth = "X-Auth-Token" in headers and "X-Auth-From" in headers # User suppplied basic auth info if using_basic_kms_auth or using_kms_auth: if using_basic_kms_auth: _from = auth["username"] token = auth["password"] else: _from = headers["X-Auth-From"] token = headers["X-Auth-Token"] try: with stats.timer("decrypt_token"): payload = keymanager.decrypt_token(token, _from) log.debug("Auth request had the following payload:" " {0}".format(payload)) role = "service" msg = "Authenticated {0} with role {1} via kms auth" msg = msg.format(_from, role) log.debug(msg) if role_has_privilege(role, f.func_name): g.auth_role = role g.username = _from return f(*args, **kwargs) else: msg = "{0} is not authorized to access {1}." msg = msg.format(_from, f.func_name) log.warning(msg) return abort(403) except keymanager.TokenDecryptionError: msg = "Access denied for {0}. Authentication Failed." msg = msg.format(_from) log.warning(msg) return abort(403) # If not using kms auth, require google auth. else: role = "user" if not role_has_privilege(role, f.func_name): return abort(403) if "email" in session.get("google_oauth2", []): if app.config["USERS_FILE"] and get_logged_in_user_email() not in users: msg = "User not authorized: {0}" log.warning(msg.format(get_logged_in_user_email())) return abort(403) else: g.auth_role = role return f(*args, **kwargs) response = make_response() if request.is_secure: secure_cookie = True else: secure_cookie = False result = _authomatic.login( WerkzeugAdapter(request, response), "google", session=session, session_saver=lambda: app.save_session(session, response), secure_cookie=secure_cookie, ) if result: if result.error: msg = "Google auth failed with error: {0}" log.error(msg.format(result.error.message)) return abort(403) if result.user: result.user.update() user = result.user email_suffix = app.config["GOOGLE_AUTH_EMAIL_SUFFIX"] if email_suffix and not user.email.endswith(email_suffix): return abort(403) session["google_oauth2"] = {} session["google_oauth2"]["email"] = user.email session["google_oauth2"]["first_name"] = user.first_name session["google_oauth2"]["last_name"] = user.last_name g.auth_role = role # TODO: find a way to save the angular args # authomatic adds url params google auth has stripped the # angular args anyway, so let's just redirect back to the # index. return redirect(url_for("index")) return response return abort(403)