def init_server_key(): from pritunl import settings from pritunl import utils resp = requests.get( 'http://127.0.0.1:9758/key', verify=False, headers={ 'User-Agent': 'pritunl', 'Accept': 'application/json', }, ) if resp.status_code != 200: raise RequestError('Vault bad status %s' % resp.status_code) cipher_data = resp.json() gcm = AESGCM(settings.local.se_encryption_key) data = gcm.decrypt( base64.b64decode(cipher_data['n']), base64.b64decode(cipher_data['d']), None, ) if nonces_contains(cipher_data['n']): raise RequestError('Vault authorization nonce replay') nonces_add(cipher_data['n']) data = json.loads(data) now = int(time.time()) diff = now - data['t'] if diff > 10 or diff < -3: raise RequestError('Vault bad timestamp %s' % data['t']) auth_data = data['n'] + '&' + str(data['t']) + '&' + \ data['h'] + '&' + data['s'] auth_signature = base64.b64encode(hmac.new( settings.local.se_authorize_key, auth_data, hashlib.sha512, ).digest()) if not utils.const_compare(auth_signature, data['a']): raise RequestError('Vault bad signature') if nonces_contains(data['n']): raise RequestError('Vault authorization nonce replay') nonces_add(data['n']) return { 'i': settings.local.host_id, 'h': data['h'].strip(), 's': data['s'].strip(), 'c': base64.b64decode(settings.local.se_client_pub_key).strip(), }
def link_state_delete(): if settings.app.demo_mode: return utils.demo_blocked() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: return flask.abort(406) auth_token = auth_token[:256] auth_timestamp = auth_timestamp[:64] auth_nonce = auth_nonce[:32] auth_signature = auth_signature[:512] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return flask.abort(408) except ValueError: return flask.abort(405) host = link.get_host(database.ObjectId(auth_token)) if not host: return flask.abort(404) auth_string = '&'.join([ auth_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path, ]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return flask.abort(413) auth_test_signature = base64.b64encode( hmac.new(host.secret.encode(), auth_string.encode(), hashlib.sha512).digest()).decode() if not utils.const_compare(auth_signature, auth_test_signature): return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: return flask.abort(409) host.set_inactive() return utils.jsonify({})
def link_state_delete(): if settings.app.demo_mode: return utils.demo_blocked() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: return flask.abort(401) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return flask.abort(401) except ValueError: return flask.abort(401) host = link.get_host(utils.ObjectId(auth_token)) if not host: return flask.abort(401) auth_string = '&'.join([ auth_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path, ]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return flask.abort(401) auth_test_signature = base64.b64encode(hmac.new( host.secret.encode(), auth_string, hashlib.sha512).digest()) if not utils.const_compare(auth_signature, auth_test_signature): return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: return flask.abort(401) host.set_inactive() return utils.jsonify({})
def setup_mongodb_put(): global setup_state setup_key = flask.request.json['setup_key'] mongodb_uri = flask.request.json['mongodb_uri'] if setup_state != 'setup': return flask.abort(404) if not utils.const_compare(setup_key, settings.local.setup_key): return utils.jsonify( { 'error': SETUP_KEY_INVALID, 'error_msg': SETUP_KEY_INVALID_MSG, }, 400) if not mongodb_uri: return utils.jsonify( { 'error': MONGODB_URI_INVALID, 'error_msg': MONGODB_URI_INVALID_MSG, }, 400) try: client = pymongo.MongoClient(mongodb_uri, connectTimeoutMS=MONGO_CONNECT_TIMEOUT) client.get_default_database() except pymongo.errors.ConfigurationError as error: if 'auth failed' in str(error): return utils.jsonify( { 'error': MONGODB_AUTH_ERROR, 'error_msg': MONGODB_AUTH_ERROR_MSG, }, 400) raise except pymongo.errors.ConnectionFailure: return utils.jsonify( { 'error': MONGODB_CONNECT_ERROR, 'error_msg': MONGODB_CONNECT_ERROR_MSG, }, 400) settings.conf.mongodb_uri = mongodb_uri settings.conf.commit() if check_db_ver(): setup_state = 'upgrade' upgrade_database() else: stop_server() return ''
def setup_mongodb_put(): global setup_state setup_key = flask.request.json['setup_key'] mongodb_uri = flask.request.json['mongodb_uri'] if setup_state != 'setup': return flask.abort(404) if not utils.const_compare(setup_key, settings.local.setup_key): return utils.jsonify({ 'error': SETUP_KEY_INVALID, 'error_msg': SETUP_KEY_INVALID_MSG, }, 400) if not mongodb_uri: return utils.jsonify({ 'error': MONGODB_URI_INVALID, 'error_msg': MONGODB_URI_INVALID_MSG, }, 400) try: client = pymongo.MongoClient(mongodb_uri, connectTimeoutMS=MONGO_CONNECT_TIMEOUT) client.get_default_database() except pymongo.errors.ConfigurationError as error: if 'auth failed' in str(error): return utils.jsonify({ 'error': MONGODB_AUTH_ERROR, 'error_msg': MONGODB_AUTH_ERROR_MSG, }, 400) raise except pymongo.errors.ConnectionFailure: return utils.jsonify({ 'error': MONGODB_CONNECT_ERROR, 'error_msg': MONGODB_CONNECT_ERROR_MSG, }, 400) settings.conf.mongodb_uri = mongodb_uri settings.conf.commit() if check_db_ver(): setup_state = 'upgrade' upgrade_database() else: stop_server() return ''
def link_state_put(): if settings.app.demo_mode: return utils.demo_blocked() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: return flask.abort(406) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return flask.abort(408) except ValueError: return flask.abort(405) host = link.get_host(utils.ObjectId(auth_token)) if not host: return flask.abort(404) auth_string = '&'.join([ auth_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path, ]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return flask.abort(413) auth_test_signature = base64.b64encode( hmac.new(host.secret.encode(), auth_string, hashlib.sha512).digest()) if not utils.const_compare(auth_signature, auth_test_signature): return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: return flask.abort(409) host.load_link() host.version = flask.request.json.get('version') host.public_address = flask.request.json.get('public_address') host.local_address = flask.request.json.get('local_address') host.address6 = flask.request.json.get('address6') data = json.dumps(host.get_state(), default=lambda x: str(x)) data += (16 - len(data) % 16) * '\x00' iv = os.urandom(16) key = hashlib.sha256(host.secret).digest() cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()).encryptor() enc_data = base64.b64encode(cipher.update(data) + cipher.finalize()) enc_signature = base64.b64encode( hmac.new(host.secret.encode(), enc_data, hashlib.sha512).digest()) resp = flask.Response(response=enc_data, mimetype='application/base64') resp.headers.add('Cache-Control', 'no-cache, no-store, must-revalidate') resp.headers.add('Pragma', 'no-cache') resp.headers.add('Expires', 0) resp.headers.add('Cipher-IV', base64.b64encode(iv)) resp.headers.add('Cipher-Signature', enc_signature) return resp
def init_master_key(cipher_data): prefix = settings.conf.mongodb_collection_prefix or '' client = pymongo.MongoClient(settings.conf.mongodb_uri, connectTimeoutMS=MONGO_CONNECT_TIMEOUT) database = client.get_default_database() settings_db = getattr(database, prefix + 'settings') doc = settings_db.find_one({ '_id': 'se', }) crypto_keys = None if doc and doc['keys']: crypto_keys = doc['keys'] ciphertext = base64.b64decode(cipher_data['c']) plaintext = settings.local.se_client_key.decrypt( ciphertext, padding.OAEP( mgf=padding.MGF1(algorithm=hashes.SHA512()), algorithm=hashes.SHA512(), label=None, ), ) client_key = plaintext.strip() nonce = utils.generate_secret_len(16) nonces_add(nonce) data = { 'n': nonce, 't': int(time.time()), 'h': cipher_data['h'], 's': cipher_data['s'], 'c': client_key, 'o': cipher_data['o'], 'm': cipher_data['m'], } auth_data = data['n'] + '&' + str(data['t']) + '&' + \ data['h'] + '&' + data['s'] + '&' + data['c'] + '&' + \ data['o'] + '&' + data['m'] if crypto_keys: data['k'] = crypto_keys auth_data += '&' + data['k'] data['a'] = base64.b64encode(hmac.new( settings.local.se_authorize_key, auth_data, hashlib.sha512, ).digest()) gcm = AESGCM(settings.local.se_encryption_key) nonce = os.urandom(12) ciphertext = gcm.encrypt( nonce, json.dumps(data), None, ) payload = { 'n': base64.b64encode(nonce), 'd': base64.b64encode(ciphertext), } resp = requests.post( 'http://127.0.0.1:9758/master', verify=False, headers={ 'User-Agent': 'pritunl', 'Accept': 'application/json', 'Content-Type': 'application/json', }, data=json.dumps(payload), ) if resp.status_code != 200: raise RequestError('Vault bad status %s' % resp.status_code) crypto_payload = resp.json() crypto_plaintext = gcm.decrypt( base64.b64decode(crypto_payload['n']), base64.b64decode(crypto_payload['d']), None, ) if nonces_contains(crypto_payload['n']): raise RequestError('Vault authorization nonce replay') nonces_add(crypto_payload['n']) crypto_data = json.loads(crypto_plaintext) now = int(time.time()) diff = now - crypto_data['t'] if diff > 10 or diff < -3: raise RequestError('Vault bad timestamp %s' % crypto_data['t']) crypto_data_auth = crypto_data['n'] + '&' + \ str(crypto_data['t']) + '&' + crypto_data['k'] crypto_data_authr = base64.b64encode(hmac.new( settings.local.se_authorize_key, crypto_data_auth, hashlib.sha512, ).digest()) if not utils.const_compare(crypto_data_authr, crypto_data['a']): raise RequestError('Invalid crypto keys signature') if nonces_contains(crypto_data['n']): raise RequestError('Vault authorization nonce replay') nonces_add(crypto_data['n']) if crypto_data['k'] != crypto_keys: settings_db.update({ '_id': 'se', }, {'$set': { 'keys': crypto_data['k'], }}, upsert=True)
def key_wg_post(org_id, user_id, server_id): org_id = org_id user_id = user_id server_id = server_id remote_addr = utils.get_remote_addr() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='Missing auth header', ) return flask.abort(406) auth_token = auth_token[:256] auth_timestamp = auth_timestamp[:64] auth_nonce = auth_nonce[:32] auth_signature = auth_signature[:512] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='Expired auth timestamp', ) return flask.abort(408) except ValueError: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='Invalid auth timestamp', ) return flask.abort(405) org = organization.get_by_id(org_id) if not org: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='Organization not found', ) return flask.abort(404) usr = org.get_user(id=user_id) if not usr: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='User not found', ) return flask.abort(404) elif not usr.sync_secret: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User missing sync secret', ) return flask.abort(410) if auth_token != usr.sync_token: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Sync token mismatch', ) return flask.abort(411) if usr.disabled: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User disabled', ) return flask.abort(403) cipher_data64 = flask.request.json.get('data') box_nonce64 = flask.request.json.get('nonce') public_key64 = flask.request.json.get('public_key') signature64 = flask.request.json.get('signature') auth_string = '&'.join([ usr.sync_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path, cipher_data64, box_nonce64, public_key64, signature64 ]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Auth string len limit exceeded', ) return flask.abort(414) auth_test_signature = base64.b64encode( hmac.new(usr.sync_secret.encode(), auth_string, hashlib.sha512).digest()) if not utils.const_compare(auth_signature, auth_test_signature): journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Auth signature mismatch', ) return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Duplicate nonce', ) return flask.abort(409) data_hash = hashlib.sha512('&'.join( [cipher_data64, box_nonce64, public_key64])).digest() try: usr.verify_sig( data_hash, base64.b64decode(signature64), ) except InvalidSignature: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Invalid rsa signature', ) return flask.abort(412) svr = usr.get_server(server_id) sender_pub_key = nacl.public.PublicKey(base64.b64decode(public_key64)) box_nonce = base64.b64decode(box_nonce64) priv_key = nacl.public.PrivateKey( base64.b64decode(svr.auth_box_private_key)) cipher_data = base64.b64decode(cipher_data64) nacl_box = nacl.public.Box(priv_key, sender_pub_key) plaintext = nacl_box.decrypt(cipher_data, box_nonce).decode('utf-8') try: nonces_collection.insert({ 'token': auth_token, 'nonce': box_nonce64, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Duplicate secondary nonce', ) return flask.abort(415) key_data = json.loads(plaintext) client_platform = utils.filter_str_uni(key_data['platform']) client_device_id = utils.filter_str_uni(key_data['device_id']) client_device_name = utils.filter_str_uni(key_data['device_name']) client_mac_addr = utils.filter_str_uni(key_data['mac_addr']) client_mac_addrs = key_data['mac_addrs'] if client_mac_addrs: client_mac_addrs = [utils.filter_str_uni(x) for x in client_mac_addrs] else: client_mac_addrs = None client_auth_token = key_data['token'].decode('utf-8') client_auth_nonce = utils.filter_str_uni(key_data['nonce']) client_auth_password = key_data['password'].decode('utf-8') client_auth_timestamp = int(key_data['timestamp']) client_wg_public_key = key_data['wg_public_key'].decode('utf-8') if len(client_wg_public_key) < 32: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Public key too short', ) return flask.abort(416) try: client_wg_public_key = base64.b64decode(client_wg_public_key) if len(client_wg_public_key) != 32: raise ValueError('Invalid length') client_wg_public_key = base64.b64encode(client_wg_public_key) except: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Public key invalid', ) return flask.abort(417) instance = server.get_instance(server_id) if not instance or instance.state != 'running': return flask.abort(429) if not instance.server.wg: return flask.abort(429) wg_keys_collection = mongo.get_collection('wg_keys') try: wg_keys_collection.insert({ '_id': client_wg_public_key, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, wg_public_key=client_wg_public_key, event_long='Duplicate wg public key', ) return flask.abort(413) clients = instance.instance_com.clients event = threading.Event() send_data = { 'allow': None, 'configuration': None, 'reason': None, } def callback(allow, data): send_data['allow'] = allow if allow: send_data['configuration'] = data else: send_data['reason'] = data event.set() clients.connect_wg( user=usr, org=org, wg_public_key=client_wg_public_key, auth_password=client_auth_password, auth_token=client_auth_token, auth_nonce=client_auth_nonce, auth_timestamp=client_auth_timestamp, platform=client_platform, device_id=client_device_id, device_name=client_device_name, mac_addr=client_mac_addr, mac_addrs=client_mac_addrs, remote_ip=remote_addr, connect_callback=callback, ) event.wait() send_nonce = nacl.utils.random(nacl.public.Box.NONCE_SIZE) nacl_box = nacl.public.Box(priv_key, sender_pub_key) send_cipher_data = nacl_box.encrypt(json.dumps(send_data), send_nonce) send_cipher_data = send_cipher_data[nacl.public.Box.NONCE_SIZE:] send_nonce64 = base64.b64encode(send_nonce) send_cipher_data64 = base64.b64encode(send_cipher_data) usr.audit_event( 'user_profile', 'User retrieved wg public key from pritunl client', remote_addr=remote_addr, ) journal.entry( journal.USER_WG_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User retrieved wg public key from pritunl client', ) sync_signature = base64.b64encode( hmac.new(usr.sync_secret.encode(), send_cipher_data64 + '&' + send_nonce64, hashlib.sha512).digest()) return utils.jsonify({ 'data': send_cipher_data64, 'nonce': send_nonce64, 'signature': sync_signature, })
def sso_callback_get(): sso_mode = settings.app.sso if sso_mode not in (AZURE_AUTH, AZURE_DUO_AUTH, AZURE_YUBICO_AUTH, GOOGLE_AUTH, GOOGLE_DUO_AUTH, GOOGLE_YUBICO_AUTH, AUTHZERO_AUTH, AUTHZERO_DUO_AUTH, AUTHZERO_YUBICO_AUTH, SLACK_AUTH, SLACK_DUO_AUTH, SLACK_YUBICO_AUTH, SAML_AUTH, SAML_DUO_AUTH, SAML_YUBICO_AUTH, SAML_OKTA_AUTH, SAML_OKTA_DUO_AUTH, SAML_OKTA_YUBICO_AUTH, SAML_ONELOGIN_AUTH, SAML_ONELOGIN_DUO_AUTH, SAML_ONELOGIN_YUBICO_AUTH): return flask.abort(405) remote_addr = utils.get_remote_addr() state = flask.request.args.get('state') sig = flask.request.args.get('sig') tokens_collection = mongo.get_collection('sso_tokens') doc = tokens_collection.find_and_modify(query={ '_id': state, }, remove=True) if not doc: return flask.abort(404) query = flask.request.query_string.split('&sig=')[0] test_sig = base64.urlsafe_b64encode(hmac.new(str(doc['secret']), query, hashlib.sha512).digest()) if not utils.const_compare(sig, test_sig): journal.entry( journal.SSO_AUTH_FAILURE, state=state, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_INVALID_CALLBACK, reason_long='Signature mismatch', ) return flask.abort(401) params = urlparse.parse_qs(query) if doc.get('type') == SAML_AUTH: username = params.get('username')[0] email = params.get('email', [None])[0] org_names = [] if params.get('org'): org_names_param = params.get('org')[0] if ';' in org_names_param: org_names = org_names_param.split(';') else: org_names = org_names_param.split(',') org_names = [x for x in org_names if x] org_names = sorted(org_names) groups = [] if params.get('groups'): groups_param = params.get('groups')[0] if ';' in groups_param: groups = groups_param.split(';') else: groups = groups_param.split(',') groups = [x for x in groups if x] groups = set(groups) if not username: return flask.abort(406) org_id = settings.app.sso_org if org_names: not_found = False for org_name in org_names: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning('Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=org_names, ) valid, org_id_new, groups2 = sso.plugin_sso_authenticate( sso_type='saml', user_name=username, user_email=email, remote_ip=remote_addr, sso_org_names=org_names, ) if valid: org_id = org_id_new or org_id else: logger.error('Saml plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Saml plugin authentication failed', ) return flask.abort(401) groups = groups | set(groups2 or []) elif doc.get('type') == SLACK_AUTH: username = params.get('username')[0] email = None user_team = params.get('team')[0] org_names = params.get('orgs', [''])[0] org_names = sorted(org_names.split(',')) if user_team != settings.app.sso_match[0]: return flask.abort(401) not_found = False org_id = settings.app.sso_org for org_name in org_names: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning('Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=org_names, ) valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='slack', user_name=username, user_email=email, remote_ip=remote_addr, sso_org_names=org_names, ) if valid: org_id = org_id_new or org_id else: logger.error('Slack plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Slack plugin authentication failed', ) return flask.abort(401) groups = set(groups or []) elif doc.get('type') == GOOGLE_AUTH: username = params.get('username')[0] email = username valid, google_groups = sso.verify_google(username) if not valid: journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_GOOGLE_FAILED, reason_long='Google authentication failed', ) return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='google', user_name=username, user_email=email, remote_ip=remote_addr, ) if valid: org_id = org_id_new or org_id else: logger.error('Google plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Google plugin authentication failed', ) return flask.abort(401) groups = set(groups or []) if settings.app.sso_google_mode == 'groups': groups = groups | set(google_groups) else: not_found = False google_groups = sorted(google_groups) for org_name in google_groups: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning('Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=google_groups, ) elif doc.get('type') == AZURE_AUTH: username = params.get('username')[0] email = None tenant, username = username.split('/', 2) if tenant != settings.app.sso_azure_directory_id: logger.error('Azure directory ID mismatch', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, azure_tenant=tenant, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_AZURE_FAILED, reason_long='Azure directory ID mismatch', ) return flask.abort(401) valid, azure_groups = sso.verify_azure(username) if not valid: journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_AZURE_FAILED, reason_long='Azure authentication failed', ) return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='azure', user_name=username, user_email=email, remote_ip=remote_addr, ) if valid: org_id = org_id_new or org_id else: logger.error('Azure plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Azure plugin authentication failed', ) return flask.abort(401) groups = set(groups or []) if settings.app.sso_azure_mode == 'groups': groups = groups | set(azure_groups) else: not_found = False azure_groups = sorted(azure_groups) for org_name in azure_groups: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning('Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=azure_groups, ) elif doc.get('type') == AUTHZERO_AUTH: username = params.get('username')[0] email = None valid, authzero_groups = sso.verify_authzero(username) if not valid: journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_AUTHZERO_FAILED, reason_long='Auth0 authentication failed', ) return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='authzero', user_name=username, user_email=email, remote_ip=remote_addr, ) if valid: org_id = org_id_new or org_id else: logger.error('Auth0 plugin authentication not valid', 'sso', username=username, ) journal.entry( journal.SSO_AUTH_FAILURE, user_name=username, remote_address=remote_addr, reason=journal.SSO_AUTH_REASON_PLUGIN_FAILED, reason_long='Auth0 plugin authentication failed', ) return flask.abort(401) groups = set(groups or []) if settings.app.sso_authzero_mode == 'groups': groups = groups | set(authzero_groups) else: not_found = False authzero_groups = sorted(authzero_groups) for org_name in authzero_groups: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning('Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=authzero_groups, ) else: logger.error('Unknown sso type', 'sso', sso_type=doc.get('type'), ) return flask.abort(401) if DUO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': DUO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': list(groups) if groups else None, 'timestamp': utils.now(), }) duo_page = static.StaticFile(settings.conf.www_path, 'duo.html', cache=False, gzip=False) sso_duo_mode = settings.app.sso_duo_mode if sso_duo_mode == 'passcode': duo_mode = 'passcode' elif sso_duo_mode == 'phone': duo_mode = 'phone' else: duo_mode = 'push' body_class = duo_mode if settings.app.theme == 'dark': body_class += ' dark' duo_page.data = duo_page.data.replace('<%= body_class %>', body_class) duo_page.data = duo_page.data.replace('<%= token %>', token) duo_page.data = duo_page.data.replace('<%= duo_mode %>', duo_mode) return duo_page.get_response() if YUBICO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': YUBICO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': list(groups) if groups else None, 'timestamp': utils.now(), }) yubico_page = static.StaticFile(settings.conf.www_path, 'yubico.html', cache=False, gzip=False) if settings.app.theme == 'dark': yubico_page.data = yubico_page.data.replace( '<body>', '<body class="dark">') yubico_page.data = yubico_page.data.replace('<%= token %>', token) return yubico_page.get_response() return _validate_user(username, email, sso_mode, org_id, groups, remote_addr, http_redirect=True)
def sso_callback_get(): sso_mode = settings.app.sso if sso_mode not in (GOOGLE_AUTH, GOOGLE_DUO_AUTH, GOOGLE_YUBICO_AUTH, SLACK_AUTH, SLACK_DUO_AUTH, SLACK_YUBICO_AUTH, SAML_AUTH, SAML_DUO_AUTH, SAML_YUBICO_AUTH, SAML_OKTA_AUTH, SAML_OKTA_DUO_AUTH, SAML_OKTA_YUBICO_AUTH, SAML_ONELOGIN_AUTH, SAML_ONELOGIN_DUO_AUTH, SAML_ONELOGIN_YUBICO_AUTH): return flask.abort(405) state = flask.request.args.get('state') sig = flask.request.args.get('sig') tokens_collection = mongo.get_collection('sso_tokens') doc = tokens_collection.find_and_modify(query={ '_id': state, }, remove=True) if not doc: return flask.abort(404) query = flask.request.query_string.split('&sig=')[0] test_sig = base64.urlsafe_b64encode(hmac.new(str(doc['secret']), query, hashlib.sha512).digest()) if not utils.const_compare(sig, test_sig): return flask.abort(401) params = urlparse.parse_qs(query) if doc.get('type') == SAML_AUTH: username = params.get('username')[0] email = params.get('email', [None])[0] org_name = params.get('org', [None])[0] if not username: return flask.abort(406) org_id = settings.app.sso_org if org_name: org = organization.get_by_name(org_name, fields=('_id')) if org: org_id = org.id valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='saml', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), sso_org_names=[org_name], ) if valid: org_id = org_id_new or org_id else: logger.error('Saml plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) elif doc.get('type') == SLACK_AUTH: username = params.get('username')[0] email = None user_team = params.get('team')[0] org_names = params.get('orgs', [''])[0] org_names = org_names.split(',') valid = sso.verify_slack(username, user_team) if not valid: return flask.abort(401) org_id = settings.app.sso_org for org_name in org_names: org = organization.get_by_name(org_name, fields=('_id')) if org: org_id = org.id break valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='slack', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), sso_org_names=org_names, ) if valid: org_id = org_id_new or org_id else: logger.error('Slack plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) else: username = params.get('username')[0] email = username valid = sso.verify_google(username) if not valid: return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='google', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), ) if valid: org_id = org_id_new or org_id else: logger.error('Google plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) if DUO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': DUO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': groups, 'timestamp': utils.now(), }) duo_page = static.StaticFile(settings.conf.www_path, 'duo.html', cache=False, gzip=False) sso_duo_mode = settings.app.sso_duo_mode if sso_duo_mode == 'passcode': duo_mode = 'passcode' elif sso_duo_mode == 'phone': duo_mode = 'phone' else: duo_mode = 'push' body_class = duo_mode if settings.app.theme == 'dark': body_class += ' dark' duo_page.data = duo_page.data.replace('<%= body_class %>', body_class) duo_page.data = duo_page.data.replace('<%= token %>', token) duo_page.data = duo_page.data.replace('<%= duo_mode %>', duo_mode) return duo_page.get_response() if YUBICO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': YUBICO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': groups, 'timestamp': utils.now(), }) yubico_page = static.StaticFile(settings.conf.www_path, 'yubico.html', cache=False, gzip=False) if settings.app.theme == 'dark': yubico_page.data = yubico_page.data.replace( '<body>', '<body class="dark">') yubico_page.data = yubico_page.data.replace('<%= token %>', token) return yubico_page.get_response() usr = user.find_user_auth(name=username, auth_type=sso_mode) if not usr: org = organization.get_by_id(org_id) if not org: return flask.abort(405) usr = org.find_user(name=username) else: org = usr.org if not usr: usr = org.new_user(name=username, email=email, type=CERT_CLIENT, auth_type=sso_mode, groups=list(groups) if groups else None) usr.audit_event('user_created', 'User created with single sign-on', remote_addr=utils.get_remote_addr()) event.Event(type=ORGS_UPDATED) event.Event(type=USERS_UPDATED, resource_id=org.id) event.Event(type=SERVERS_UPDATED) else: if usr.disabled: return flask.abort(403) if groups and groups - set(usr.groups or []): usr.groups = list(set(usr.groups or []) | groups) usr.commit('groups') if usr.auth_type != sso_mode: usr.auth_type = sso_mode usr.commit('auth_type') key_link = org.create_user_key_link(usr.id, one_time=True) usr.audit_event('user_profile', 'User profile viewed from single sign-on', remote_addr=utils.get_remote_addr(), ) return utils.redirect(utils.get_url_root() + key_link['view_url'])
def key_sync_get(org_id, user_id, server_id, key_hash): if not settings.user.conf_sync: return utils.jsonify({}) if not settings.local.sub_active: return utils.jsonify({}, status_code=480) utils.rand_sleep() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: return flask.abort(401) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return flask.abort(401) except ValueError: return flask.abort(401) org = organization.get_by_id(org_id) if not org: return flask.abort(401) usr = org.get_user(id=user_id) if not usr: return flask.abort(401) elif not usr.sync_secret: return flask.abort(401) if auth_token != usr.sync_token: return flask.abort(401) if usr.disabled: return flask.abort(403) auth_string = '&'.join([ usr.sync_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return flask.abort(401) auth_test_signature = base64.b64encode(hmac.new( usr.sync_secret.encode(), auth_string, hashlib.sha512).digest()) if not utils.const_compare(auth_signature, auth_test_signature): return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: return flask.abort(401) key_conf = usr.sync_conf(server_id, key_hash) if key_conf: usr.audit_event('user_profile', 'User profile synced from pritunl client', remote_addr=utils.get_remote_addr(), ) sync_signature = base64.b64encode(hmac.new( usr.sync_secret.encode(), key_conf['conf'], hashlib.sha512).digest()) return utils.jsonify({ 'signature': sync_signature, 'conf': key_conf['conf'], }) return utils.jsonify({})
def key_wg_put(org_id, user_id, server_id): org_id = org_id user_id = user_id server_id = server_id remote_addr = utils.get_remote_addr() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='Missing auth header', ) return flask.abort(406) auth_token = auth_token[:256] auth_timestamp = auth_timestamp[:64] auth_nonce = auth_nonce[:32] auth_signature = auth_signature[:512] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='Expired auth timestamp', ) return flask.abort(408) except ValueError: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='Invalid auth timestamp', ) return flask.abort(405) org = organization.get_by_id(org_id) if not org: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='Organization not found', ) return flask.abort(404) usr = org.get_user(id=user_id) if not usr: journal.entry( journal.USER_WG_FAILURE, remote_address=remote_addr, event_long='User not found', ) return flask.abort(404) elif not usr.sync_secret: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User missing sync secret', ) return flask.abort(410) if auth_token != usr.sync_token: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Sync token mismatch', ) return flask.abort(411) if usr.disabled: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User disabled', ) return flask.abort(403) cipher_data64 = flask.request.json.get('data') box_nonce64 = flask.request.json.get('nonce') public_key64 = flask.request.json.get('public_key') signature64 = flask.request.json.get('signature') auth_string = '&'.join([ usr.sync_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path, cipher_data64, box_nonce64, public_key64, signature64]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Auth string len limit exceeded', ) return flask.abort(414) auth_test_signature = base64.b64encode(hmac.new( usr.sync_secret.encode(), auth_string.encode(), hashlib.sha512).digest()).decode() if not utils.const_compare(auth_signature, auth_test_signature): journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Auth signature mismatch', ) return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Duplicate nonce', ) return flask.abort(409) data_hash = hashlib.sha512( '&'.join([cipher_data64, box_nonce64, public_key64]).encode(), ).digest() try: usr.verify_sig( data_hash, base64.b64decode(signature64), ) except InvalidSignature: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Invalid rsa signature', ) return flask.abort(412) svr = usr.get_server(server_id) sender_pub_key = nacl.public.PublicKey( base64.b64decode(public_key64)) box_nonce = base64.b64decode(box_nonce64) priv_key = nacl.public.PrivateKey( base64.b64decode(svr.auth_box_private_key)) cipher_data = base64.b64decode(cipher_data64) nacl_box = nacl.public.Box(priv_key, sender_pub_key) plaintext = nacl_box.decrypt(cipher_data, box_nonce).decode() try: nonces_collection.insert({ 'token': auth_token, 'nonce': box_nonce64, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Duplicate secondary nonce', ) return flask.abort(412) key_data = json.loads(plaintext) client_wg_public_key = utils.filter_str(key_data['wg_public_key']) if len(client_wg_public_key) < 32: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Public key too short', ) return flask.abort(415) try: client_wg_public_key = base64.b64decode(client_wg_public_key) if len(client_wg_public_key) != 32: raise ValueError('Invalid length') client_wg_public_key = base64.b64encode(client_wg_public_key) except: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Public key invalid', ) return flask.abort(416) wg_keys_collection = mongo.get_collection('wg_keys') wg_key_doc = wg_keys_collection.find_one({ '_id': client_wg_public_key, }) if not wg_key_doc: journal.entry( journal.USER_WG_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Public key not found', ) return flask.abort(417) instance = server.get_instance(server_id) if not instance or instance.state != 'running': return flask.abort(429) if not instance.server.wg: return flask.abort(429) clients = instance.instance_com.clients status = clients.ping_wg( user=usr, org=org, wg_public_key=client_wg_public_key, remote_ip=remote_addr, ) send_data = { 'status': status, 'timestamp': int(utils.time_now()), } send_nonce = nacl.utils.random(nacl.public.Box.NONCE_SIZE) nacl_box = nacl.public.Box(priv_key, sender_pub_key) send_cipher_data = nacl_box.encrypt( json.dumps(send_data).encode(), send_nonce) send_cipher_data = send_cipher_data[nacl.public.Box.NONCE_SIZE:] send_nonce64 = base64.b64encode(send_nonce).decode() send_cipher_data64 = base64.b64encode(send_cipher_data).decode() journal.entry( journal.USER_WG_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User wg ping from pritunl client', ) sync_signature = base64.b64encode(hmac.new( usr.sync_secret.encode(), (send_cipher_data64 + '&' + send_nonce64).encode(), hashlib.sha512).digest()).decode() return utils.jsonify({ 'data': send_cipher_data64, 'nonce': send_nonce64, 'signature': sync_signature, })
def sso_callback_get(): sso_mode = settings.app.sso if sso_mode not in (GOOGLE_AUTH, GOOGLE_DUO_AUTH, GOOGLE_YUBICO_AUTH, SLACK_AUTH, SLACK_DUO_AUTH, SLACK_YUBICO_AUTH, SAML_AUTH, SAML_DUO_AUTH, SAML_YUBICO_AUTH, SAML_OKTA_AUTH, SAML_OKTA_DUO_AUTH, SAML_OKTA_YUBICO_AUTH, SAML_ONELOGIN_AUTH, SAML_ONELOGIN_DUO_AUTH, SAML_ONELOGIN_YUBICO_AUTH): return flask.abort(405) state = flask.request.args.get('state') sig = flask.request.args.get('sig') tokens_collection = mongo.get_collection('sso_tokens') doc = tokens_collection.find_and_modify(query={ '_id': state, }, remove=True) if not doc: return flask.abort(404) query = flask.request.query_string.split('&sig=')[0] test_sig = base64.urlsafe_b64encode(hmac.new(str(doc['secret']), query, hashlib.sha512).digest()) if not utils.const_compare(sig, test_sig): return flask.abort(401) params = urlparse.parse_qs(query) if doc.get('type') == SAML_AUTH: username = params.get('username')[0] email = params.get('email', [None])[0] org_name = params.get('org', [None])[0] if not username: return flask.abort(406) org_id = settings.app.sso_org if org_name: org = organization.get_by_name(org_name, fields=('_id')) if org: org_id = org.id valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='saml', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), sso_org_names=[org_name], ) if valid: org_id = org_id_new or org_id else: logger.error('Saml plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) elif doc.get('type') == SLACK_AUTH: username = params.get('username')[0] email = None user_team = params.get('team')[0] org_names = params.get('orgs', [''])[0] org_names = org_names.split(',') valid = sso.verify_slack(username, user_team) if not valid: return flask.abort(401) org_id = settings.app.sso_org for org_name in org_names: org = organization.get_by_name(org_name, fields=('_id')) if org: org_id = org.id break valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='slack', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), sso_org_names=org_names, ) if valid: org_id = org_id_new or org_id else: logger.error('Slack plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) else: username = params.get('username')[0] email = username valid = sso.verify_google(username) if not valid: return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='google', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), ) if valid: org_id = org_id_new or org_id else: logger.error('Google plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) if DUO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': DUO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': groups, 'timestamp': utils.now(), }) duo_page = static.StaticFile(settings.conf.www_path, 'duo.html', cache=False, gzip=False) sso_duo_mode = settings.app.sso_duo_mode if sso_duo_mode == 'passcode': duo_mode = 'passcode' elif sso_duo_mode == 'phone': duo_mode = 'phone' else: duo_mode = 'push' body_class = duo_mode if settings.app.theme == 'dark': body_class += ' dark' duo_page.data = duo_page.data.replace('<%= body_class %>', body_class) duo_page.data = duo_page.data.replace('<%= token %>', token) duo_page.data = duo_page.data.replace('<%= duo_mode %>', duo_mode) return duo_page.get_response() if YUBICO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': YUBICO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': groups, 'timestamp': utils.now(), }) yubico_page = static.StaticFile(settings.conf.www_path, 'yubico.html', cache=False, gzip=False) if settings.app.theme == 'dark': yubico_page.data = yubico_page.data.replace( '<body>', '<body class="dark">') yubico_page.data = yubico_page.data.replace('<%= token %>', token) return yubico_page.get_response() org = organization.get_by_id(org_id) if not org: return flask.abort(405) usr = org.find_user(name=username) if not usr: usr = org.new_user(name=username, email=email, type=CERT_CLIENT, auth_type=sso_mode, groups=list(groups) if groups else None) usr.audit_event('user_created', 'User created with single sign-on', remote_addr=utils.get_remote_addr()) event.Event(type=ORGS_UPDATED) event.Event(type=USERS_UPDATED, resource_id=org.id) event.Event(type=SERVERS_UPDATED) else: if usr.disabled: return flask.abort(403) if groups and groups - set(usr.groups or []): usr.groups = list(set(usr.groups or []) | groups) usr.commit('groups') if usr.auth_type != sso_mode: usr.auth_type = sso_mode usr.commit('auth_type') key_link = org.create_user_key_link(usr.id, one_time=True) usr.audit_event('user_profile', 'User profile viewed from single sign-on', remote_addr=utils.get_remote_addr(), ) return utils.redirect(utils.get_url_root() + key_link['view_url'])
def key_sync_get(org_id, user_id, server_id, key_hash): if not settings.user.conf_sync: return utils.jsonify({}) if not settings.local.sub_active: return utils.jsonify({}, status_code=480) utils.rand_sleep() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: return flask.abort(401) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return flask.abort(401) except ValueError: return flask.abort(401) org = organization.get_by_id(org_id) if not org: return flask.abort(401) user = org.get_user(id=user_id) if not user: return flask.abort(401) elif not user.sync_secret: return flask.abort(401) if auth_token != user.sync_token: return flask.abort(401) if user.disabled: return flask.abort(403) auth_string = '&'.join([ user.sync_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return flask.abort(401) auth_test_signature = base64.b64encode(hmac.new( user.sync_secret.encode(), auth_string, hashlib.sha512).digest()) if not utils.const_compare(auth_signature, auth_test_signature): return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: return flask.abort(401) key_conf = user.sync_conf(server_id, key_hash) if key_conf: user.audit_event('user_profile', 'User profile synced from pritunl client', remote_addr=utils.get_remote_addr(), ) sync_signature = base64.b64encode(hmac.new( user.sync_secret.encode(), key_conf['conf'], hashlib.sha512).digest()) return utils.jsonify({ 'signature': sync_signature, 'conf': key_conf['conf'], }) return utils.jsonify({})
def key_sync_get(org_id, user_id, server_id, key_hash): remote_addr = utils.get_remote_addr() if not settings.user.conf_sync: return utils.jsonify({}) if not settings.local.sub_active: return utils.jsonify({}, status_code=480) utils.rand_sleep() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Missing auth header', ) return flask.abort(406) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Expired auth timestamp', ) return flask.abort(408) except ValueError: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Invalid auth timestamp', ) return flask.abort(405) org = organization.get_by_id(org_id) if not org: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Organization not found', ) return flask.abort(404) usr = org.get_user(id=user_id) if not usr: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='User not found', ) return flask.abort(404) elif not usr.sync_secret: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User missing sync secret', ) return flask.abort(410) if auth_token != usr.sync_token: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Sync token mismatch', ) return flask.abort(410) if usr.disabled: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User disabled', ) return flask.abort(403) auth_string = '&'.join([ usr.sync_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Auth string len limit exceeded', ) return flask.abort(413) auth_test_signature = base64.b64encode(hmac.new( usr.sync_secret.encode(), auth_string, hashlib.sha512).digest()) if not utils.const_compare(auth_signature, auth_test_signature): journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Sync signature mismatch', ) return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Duplicate key', ) return flask.abort(409) key_conf = usr.sync_conf(server_id, key_hash) if key_conf: usr.audit_event('user_profile', 'User profile synced from pritunl client', remote_addr=remote_addr, ) journal.entry( journal.USER_SYNC_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User profile synced from pritunl client', ) sync_signature = base64.b64encode(hmac.new( usr.sync_secret.encode(), key_conf['conf'], hashlib.sha512).digest()) return utils.jsonify({ 'signature': sync_signature, 'conf': key_conf['conf'], }) return utils.jsonify({})
def sso_callback_get(): sso_mode = settings.app.sso if sso_mode not in (AZURE_AUTH, AZURE_DUO_AUTH, AZURE_YUBICO_AUTH, GOOGLE_AUTH, GOOGLE_DUO_AUTH, GOOGLE_YUBICO_AUTH, SLACK_AUTH, SLACK_DUO_AUTH, SLACK_YUBICO_AUTH, SAML_AUTH, SAML_DUO_AUTH, SAML_YUBICO_AUTH, SAML_OKTA_AUTH, SAML_OKTA_DUO_AUTH, SAML_OKTA_YUBICO_AUTH, SAML_ONELOGIN_AUTH, SAML_ONELOGIN_DUO_AUTH, SAML_ONELOGIN_YUBICO_AUTH): return flask.abort(405) state = flask.request.args.get('state') sig = flask.request.args.get('sig') tokens_collection = mongo.get_collection('sso_tokens') doc = tokens_collection.find_and_modify(query={ '_id': state, }, remove=True) if not doc: return flask.abort(404) query = flask.request.query_string.split('&sig=')[0] test_sig = base64.urlsafe_b64encode( hmac.new(str(doc['secret']), query, hashlib.sha512).digest()) if not utils.const_compare(sig, test_sig): return flask.abort(401) params = urlparse.parse_qs(query) if doc.get('type') == SAML_AUTH: username = params.get('username')[0] email = params.get('email', [None])[0] org_names = [] if params.get('org'): org_names_param = params.get('org')[0] if ';' in org_names_param: org_names = org_names_param.split(';') else: org_names = org_names_param.split(',') org_names = [x for x in org_names if x] org_names = sorted(org_names) groups = [] if params.get('groups'): groups_param = params.get('groups')[0] if ';' in groups_param: groups = groups_param.split(';') else: groups = groups_param.split(',') groups = [x for x in groups if x] groups = set(groups) if not username: return flask.abort(406) org_id = settings.app.sso_org if org_names: not_found = False for org_name in org_names: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning( 'Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=org_names, ) valid, org_id_new, groups2 = sso.plugin_sso_authenticate( sso_type='saml', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), sso_org_names=org_names, ) if valid: org_id = org_id_new or org_id else: logger.error( 'Saml plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) groups = groups | set(groups2 or []) elif doc.get('type') == SLACK_AUTH: username = params.get('username')[0] email = None user_team = params.get('team')[0] org_names = params.get('orgs', [''])[0] org_names = sorted(org_names.split(',')) if user_team != settings.app.sso_match[0]: return flask.abort(401) not_found = False org_id = settings.app.sso_org for org_name in org_names: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning( 'Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=org_names, ) valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='slack', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), sso_org_names=org_names, ) if valid: org_id = org_id_new or org_id else: logger.error( 'Slack plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) groups = set(groups or []) elif doc.get('type') == GOOGLE_AUTH: username = params.get('username')[0] email = username valid, google_groups = sso.verify_google(username) if not valid: return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='google', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), ) if valid: org_id = org_id_new or org_id else: logger.error( 'Google plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) groups = set(groups or []) if settings.app.sso_google_mode == 'groups': groups = groups | set(google_groups) else: not_found = False google_groups = sorted(google_groups) for org_name in google_groups: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning( 'Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=google_groups, ) elif doc.get('type') == AZURE_AUTH: username = params.get('username')[0] email = None tenant, username = username.split('/', 2) if tenant != settings.app.sso_azure_directory_id: logger.error( 'Azure directory ID mismatch', 'sso', username=username, ) return flask.abort(401) valid, azure_groups = sso.verify_azure(username) if not valid: return flask.abort(401) org_id = settings.app.sso_org valid, org_id_new, groups = sso.plugin_sso_authenticate( sso_type='azure', user_name=username, user_email=email, remote_ip=utils.get_remote_addr(), ) if valid: org_id = org_id_new or org_id else: logger.error( 'Azure plugin authentication not valid', 'sso', username=username, ) return flask.abort(401) groups = set(groups or []) if settings.app.sso_azure_mode == 'groups': groups = groups | set(azure_groups) else: not_found = False azure_groups = sorted(azure_groups) for org_name in azure_groups: org = organization.get_by_name( utils.filter_unicode(org_name), fields=('_id'), ) if org: not_found = False org_id = org.id break else: not_found = True if not_found: logger.warning( 'Supplied org names do not exists', 'sso', sso_type=doc.get('type'), user_name=username, user_email=email, org_names=azure_groups, ) else: logger.error( 'Unknown sso type', 'sso', sso_type=doc.get('type'), ) return flask.abort(401) if DUO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': DUO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': list(groups) if groups else None, 'timestamp': utils.now(), }) duo_page = static.StaticFile(settings.conf.www_path, 'duo.html', cache=False, gzip=False) sso_duo_mode = settings.app.sso_duo_mode if sso_duo_mode == 'passcode': duo_mode = 'passcode' elif sso_duo_mode == 'phone': duo_mode = 'phone' else: duo_mode = 'push' body_class = duo_mode if settings.app.theme == 'dark': body_class += ' dark' duo_page.data = duo_page.data.replace('<%= body_class %>', body_class) duo_page.data = duo_page.data.replace('<%= token %>', token) duo_page.data = duo_page.data.replace('<%= duo_mode %>', duo_mode) return duo_page.get_response() if YUBICO_AUTH in sso_mode: token = utils.generate_secret() tokens_collection = mongo.get_collection('sso_tokens') tokens_collection.insert({ '_id': token, 'type': YUBICO_AUTH, 'username': username, 'email': email, 'org_id': org_id, 'groups': list(groups) if groups else None, 'timestamp': utils.now(), }) yubico_page = static.StaticFile(settings.conf.www_path, 'yubico.html', cache=False, gzip=False) if settings.app.theme == 'dark': yubico_page.data = yubico_page.data.replace( '<body>', '<body class="dark">') yubico_page.data = yubico_page.data.replace('<%= token %>', token) return yubico_page.get_response() return _validate_user(username, email, sso_mode, org_id, groups, http_redirect=True)
def key_sync_get(org_id, user_id, server_id, key_hash): remote_addr = utils.get_remote_addr() if not settings.user.conf_sync: return utils.jsonify({}) if not settings.local.sub_active: return utils.jsonify({}, status_code=480) utils.rand_sleep() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Missing auth header', ) return flask.abort(406) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Expired auth timestamp', ) return flask.abort(408) except ValueError: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Invalid auth timestamp', ) return flask.abort(405) org = organization.get_by_id(org_id) if not org: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='Organization not found', ) return flask.abort(404) usr = org.get_user(id=user_id) if not usr: journal.entry( journal.USER_SYNC_FAILURE, remote_address=remote_addr, event_long='User not found', ) return flask.abort(404) elif not usr.sync_secret: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User missing sync secret', ) return flask.abort(410) if auth_token != usr.sync_token: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Sync token mismatch', ) return flask.abort(410) if usr.disabled: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='User disabled', ) return flask.abort(403) auth_string = '&'.join([ usr.sync_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path ]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Auth string len limit exceeded', ) return flask.abort(413) auth_test_signature = base64.b64encode( hmac.new(usr.sync_secret.encode(), auth_string, hashlib.sha512).digest()) if not utils.const_compare(auth_signature, auth_test_signature): journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Sync signature mismatch', ) return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: journal.entry( journal.USER_SYNC_FAILURE, usr.journal_data, remote_address=remote_addr, event_long='Duplicate key', ) return flask.abort(409) key_conf = usr.sync_conf(server_id, key_hash) if key_conf: usr.audit_event( 'user_profile', 'User profile synced from pritunl client', remote_addr=remote_addr, ) journal.entry( journal.USER_SYNC_SUCCESS, usr.journal_data, remote_address=remote_addr, event_long='User profile synced from pritunl client', ) sync_signature = base64.b64encode( hmac.new(usr.sync_secret.encode(), key_conf['conf'], hashlib.sha512).digest()) return utils.jsonify({ 'signature': sync_signature, 'conf': key_conf['conf'], }) return utils.jsonify({})
def link_state_put(): if settings.app.demo_mode: return utils.demo_blocked() auth_token = flask.request.headers.get('Auth-Token', None) auth_timestamp = flask.request.headers.get('Auth-Timestamp', None) auth_nonce = flask.request.headers.get('Auth-Nonce', None) auth_signature = flask.request.headers.get('Auth-Signature', None) if not auth_token or not auth_timestamp or not auth_nonce or \ not auth_signature: return flask.abort(401) auth_nonce = auth_nonce[:32] try: if abs(int(auth_timestamp) - int(utils.time_now())) > \ settings.app.auth_time_window: return flask.abort(401) except ValueError: return flask.abort(401) host = link.get_host(utils.ObjectId(auth_token)) if not host: return flask.abort(401) auth_string = '&'.join([ auth_token, auth_timestamp, auth_nonce, flask.request.method, flask.request.path, ]) if len(auth_string) > AUTH_SIG_STRING_MAX_LEN: return flask.abort(401) auth_test_signature = base64.b64encode(hmac.new( host.secret.encode(), auth_string, hashlib.sha512).digest()) if not utils.const_compare(auth_signature, auth_test_signature): return flask.abort(401) nonces_collection = mongo.get_collection('auth_nonces') try: nonces_collection.insert({ 'token': auth_token, 'nonce': auth_nonce, 'timestamp': utils.now(), }) except pymongo.errors.DuplicateKeyError: return flask.abort(401) host.load_link() host.version = flask.request.json.get('version') host.public_address = flask.request.json.get('public_address') data = json.dumps(host.get_state(), default=lambda x: str(x)) data += (16 - len(data) % 16) * '\x00' iv = Crypto.Random.new().read(16) key = hashlib.sha256(host.secret).digest() cipher = Crypto.Cipher.AES.new( key, Crypto.Cipher.AES.MODE_CBC, iv, ) enc_data = base64.b64encode(cipher.encrypt(data)) enc_signature = base64.b64encode(hmac.new( host.secret.encode(), enc_data, hashlib.sha512).digest()) resp = flask.Response(response=enc_data, mimetype='application/base64') resp.headers.add('Cache-Control', 'no-cache, no-store, must-revalidate') resp.headers.add('Pragma', 'no-cache') resp.headers.add('Expires', 0) resp.headers.add('Cipher-IV', base64.b64encode(iv)) resp.headers.add('Cipher-Signature', enc_signature) return resp