From f5a8a19659f0db91de7428e5a9af5c5dfeee5553 Mon Sep 17 00:00:00 2001 From: Patrick Decat Date: Wed, 22 Jul 2020 15:53:32 +0200 Subject: [PATCH 1/3] Support Duo bypassing MFA requests --- aws_adfs/_duo_authenticator.py | 114 ++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/aws_adfs/_duo_authenticator.py b/aws_adfs/_duo_authenticator.py index d88f8fdb..47a6ea2e 100644 --- a/aws_adfs/_duo_authenticator.py +++ b/aws_adfs/_duo_authenticator.py @@ -46,7 +46,7 @@ def extract(html_response, ssl_verification_enabled, u2f_trigger_default, sessio roles_page_url = _action_url_on_validation_success(html_response) click.echo("Sending request for authentication", err=True) - (sid, preferred_factor, preferred_device, u2f_supported), initiated = _initiate_authentication( + (sid, preferred_factor, preferred_device, u2f_supported, auth_signature), initiated = _initiate_authentication( duo_host, duo_request_signature, roles_page_url, @@ -54,57 +54,58 @@ def extract(html_response, ssl_verification_enabled, u2f_trigger_default, sessio ssl_verification_enabled ) if initiated: - click.echo("Waiting for additional authentication", err=True) - - rq = queue.Queue() - auth_count = 0 - if u2f_supported: - # Trigger U2F authentication - auth_count += 1 - t = Thread( - target=_perform_authentication_transaction, - args=( - duo_host, - sid, - preferred_factor, - preferred_device, - True, - session, - ssl_verification_enabled, - rq, + if auth_signature is None: + click.echo("Waiting for additional authentication", err=True) + + rq = queue.Queue() + auth_count = 0 + if u2f_supported: + # Trigger U2F authentication + auth_count += 1 + t = Thread( + target=_perform_authentication_transaction, + args=( + duo_host, + sid, + preferred_factor, + preferred_device, + True, + session, + ssl_verification_enabled, + rq, + ) ) - ) - t.daemon = True - t.start() - - if u2f_trigger_default or not u2f_supported: - # Trigger default authentication (call or push) concurrently to U2F - auth_count += 1 - t = Thread( - target=_perform_authentication_transaction, - args=( - duo_host, - sid, - preferred_factor, - preferred_device, - False, - session, - ssl_verification_enabled, - rq, + t.daemon = True + t.start() + + if u2f_trigger_default or not u2f_supported: + # Trigger default authentication (call or push) concurrently to U2F + auth_count += 1 + t = Thread( + target=_perform_authentication_transaction, + args=( + duo_host, + sid, + preferred_factor, + preferred_device, + False, + session, + ssl_verification_enabled, + rq, + ) ) - ) - t.daemon = True - t.start() - - while "Wait for responses": - auth_signature = rq.get() - auth_count -= 1 - if auth_signature != "cancelled": - break - if auth_count < 1: - click.echo("All authentication methods cancelled, aborting.") - exit(-2) + t.daemon = True + t.start() + while "Wait for responses": + auth_signature = rq.get() + auth_count -= 1 + if auth_signature != "cancelled": + break + if auth_count < 1: + click.echo("All authentication methods cancelled, aborting.") + exit(-2) + click.echo('Going for aws roles', err=True) return _retrieve_roles_page( roles_page_url, @@ -480,20 +481,27 @@ def _initiate_authentication(duo_host, duo_request_signature, roles_page_url, se response.text)) if response.status_code != 200 or response.url is None: - return (None, None, None, None), False + return (None, None, None, None, None), False o = urlparse(response.url) query = parse_qs(o.query) + html_response = ET.fromstring(response.text, ET.HTMLParser()) if 'sid' not in query: - return (None, None, None, None), False + # No need for second factor authentification, Duo directly returned the authentication cookie + return (None, None, None, None, _js_cookie(html_response)), True sid = query['sid'] - html_response = ET.fromstring(response.text, ET.HTMLParser()) preferred_factor = _preferred_factor(html_response) preferred_device = _preferred_device(html_response) u2f_supported = _u2f_supported(html_response) - return (sid, preferred_factor, preferred_device, u2f_supported), True + return (sid, preferred_factor, preferred_device, u2f_supported, None), True + + +def _js_cookie(html_response): + js_cookie_query = './/input[@name="js_cookie"]' + element = html_response.find(js_cookie_query) + return element is not None and element.get('value') or None def _preferred_factor(html_response): From 139c285532830bdfed2de6bc88403a9ea6fdd863 Mon Sep 17 00:00:00 2001 From: Patrick Decat Date: Mon, 12 Oct 2020 22:20:15 +0200 Subject: [PATCH 2/3] Add python 3.9 to Github Actions too --- .github/workflows/build.yml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0c040ba..e9734da2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,9 +5,9 @@ name: Build on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: build: @@ -15,20 +15,20 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8] + python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install libkrb5-dev - run: sudo apt-get install libkrb5-dev - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install aws-adfs['test'] - - name: Test with pytest - run: | - pytest + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install libkrb5-dev + run: sudo apt-get install libkrb5-dev + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install aws-adfs['test'] + - name: Test with pytest + run: | + pytest From 7fb92e2a6bd8d624c55f4a1d96ee2ac171195e67 Mon Sep 17 00:00:00 2001 From: Patrick Decat Date: Mon, 12 Oct 2020 22:31:32 +0200 Subject: [PATCH 3/3] Installing lxml from source is required for python 3.9 until a precompiled Wheel archive is available from PyPI, see https://pypi.org/project/lxml/#files --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9734da2..79e01314 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,11 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install libkrb5-dev - run: sudo apt-get install libkrb5-dev + run: sudo apt-get -y install libkrb5-dev + - name: Install libxml2-dev and libxslt1-dev for python 3.9 + # Installing lxml from source is required for python 3.9 until a precompiled Wheel archive is available from PyPI, see https://pypi.org/project/lxml/#files + run: sudo apt-get -y install libxml2-dev libxslt1-dev + if: matrix.python-version == 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip