Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PXP-6617 Remove scopes from aud claim in tokens #839

Merged
merged 34 commits into from
Jun 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7a51d60
fix(jwt-aud): Rm scopes from aud claim in id tokens
vpsx Aug 26, 2020
e9548d8
feat(scope): Add JWT scope validation
vpsx Sep 8, 2020
b510bfc
fix(aud): Pass aud to validate_jwt as string, not set
vpsx Sep 8, 2020
04f7da8
feat(scope): pass scope=None when validating session tokens
vpsx Sep 8, 2020
90ecb17
feat(scopes): Add scope claim to oidc tokens
vpsx Sep 10, 2020
9de4879
feat(scope): pass scopes to generate_[implicit,token]_response
vpsx Sep 10, 2020
6df3762
fix(aud): Validate aud claim in refresh token grant flow
vpsx Sep 10, 2020
5e731d0
feat(scope): add scope claim to claims_supported
vpsx Sep 10, 2020
67fd871
fix(aud): Look for scopes in scope not aud claim when validating refr…
vpsx Sep 10, 2020
44b977e
fix(aud): Skip aud validation in login_required fn
vpsx Sep 11, 2020
7760681
fix(aud-to-scope): authutils require_auth_header now takes scope arg …
vpsx Sep 14, 2020
72796f8
fix(aud-scope): Don't include aud in API keys; fix API key validation
vpsx Sep 16, 2020
315d470
fix(scope): change scope param to set in require_auth_header
vpsx Sep 21, 2020
5d70405
fix(aud-scope): Fix validate_request argument
vpsx Sep 21, 2020
42539f3
fix(aud-scope): Don't validate aud when blacklisting tokens
vpsx Sep 21, 2020
21cfe03
fix(aud-scope): Get scope from new scope claim in refresh token grant
vpsx Sep 21, 2020
226318d
fix(aud): Skip aud validation for refresh tokens
vpsx Sep 21, 2020
d229a1a
test(aud-scope): Update aud and scope claims in bunch of fixtures
vpsx Sep 21, 2020
518dbab
test(aud-scope): Update validate_jwt calls in bunch of tests
vpsx Sep 21, 2020
a084977
Temporarily install authutils requirement from branch
vpsx Sep 23, 2020
9da4dbe
fix(aud): Include issuer in aud claim for all tokens
vpsx Oct 8, 2020
5048aa5
fix(aud): In validate_jwt, set aud argument default to issuer
vpsx Oct 8, 2020
e4dc5b8
test(aud): Switch test claims fixtures to have iss in aud, not client_id
vpsx Oct 8, 2020
963f8da
fix(aud): Enable aud claim validation in has_oauth/login_required
vpsx Oct 8, 2020
162d719
fix(aud): Enable aud validation for refresh tokens
vpsx Oct 9, 2020
4dfb3cf
fix(aud): Update remaining validate_jwt calls
vpsx Oct 9, 2020
0df7db4
docs(aud): Remove techdebt item! about using aud claim for scopes
vpsx Oct 9, 2020
75f17cf
fix(aud): Update validate_request calls
vpsx Feb 16, 2021
aa654ba
fix(aud-scope): Use isinstance() not type()
vpsx Apr 23, 2021
20c80a4
chore(aud-scope): bump authutils to ^6.0.0
vpsx Apr 29, 2021
171dc20
chore(black): pre-commit autoupdate and fix black
vpsx Apr 29, 2021
9bc3766
fix(aud-scope): Give up and keep scopes in aud claim in access tkns f…
vpsx May 26, 2021
383ef41
fix(aud-scope): Allow old style refresh tokens in this tag
vpsx Jun 2, 2021
e6238eb
fix(aud-scope): Allow old style API keys in this tag
vpsx Jun 4, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
repos:
- repo: git@github.com:Yelp/detect-secrets
rev: v0.13.1
rev: v1.1.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
exclude: poetry.lock
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.5.0
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: no-commit-to-branch
args: [--branch, develop, --branch, master, --pattern, release/.*]
- repo: https://github.com/psf/black
rev: stable
rev: 21.5b1
hooks:
- id: black
131 changes: 45 additions & 86 deletions .secrets.baseline
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
{
"exclude": {
"files": "poetry.lock",
"lines": null
},
"generated_at": "2021-05-25T21:22:19Z",
"version": "1.1.0",
"plugins_used": [
{
"name": "ArtifactoryDetector"
Expand Down Expand Up @@ -80,6 +76,12 @@
{
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
},
{
"path": "detect_secrets.filters.heuristic.is_lock_file"
},
{
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
},
{
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
},
Expand All @@ -89,6 +91,9 @@
{
"path": "detect_secrets.filters.heuristic.is_sequential_string"
},
{
"path": "detect_secrets.filters.heuristic.is_swagger_file"
},
{
"path": "detect_secrets.filters.heuristic.is_templated_secret"
}
Expand All @@ -110,6 +115,13 @@
"hashed_secret": "98c144f5ecbb4dbe575147a39698b6be1a5649dd",
"is_verified": false,
"line_number": 66
},
{
"type": "Secret Keyword",
"filename": "fence/blueprints/storage_creds/other.py",
"hashed_secret": "98c144f5ecbb4dbe575147a39698b6be1a5649dd",
"is_verified": false,
"line_number": 66
}
],
"fence/config-default.yaml": [
Expand All @@ -119,13 +131,6 @@
"hashed_secret": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
"is_verified": false,
"line_number": 31
},
{
"type": "Secret Keyword",
"filename": "fence/config-default.yaml",
"hashed_secret": "dd29ecf524b030a65261e3059c48ab9e1ecb2585",
"is_verified": false,
"line_number": 101
}
],
"fence/local_settings.example.py": [
Expand Down Expand Up @@ -167,91 +172,52 @@
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
"is_verified": false,
"line_number": 248
}
],
"openapis/swagger.yaml": [
{
"type": "Private Key",
"filename": "openapis/swagger.yaml",
"hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9",
"is_verified": false,
"line_number": 1927
},
{
"type": "Secret Keyword",
"filename": "openapis/swagger.yaml",
"hashed_secret": "8cb81a55dff48721f04fe341f33ee5b623dd9144",
"is_verified": false,
"line_number": 1927
},
{
"type": "Secret Keyword",
"filename": "openapis/swagger.yaml",
"hashed_secret": "41c39979fd01095376b3e9456f1058c33483dbbe",
"is_verified": false,
"line_number": 1994
},
{
"type": "JSON Web Token",
"filename": "openapis/swagger.yaml",
"hashed_secret": "d6b66ddd9ea7dbe760114bfe9a97352a5e139134",
"filename": "fence/utils.py",
"hashed_secret": "8954f53c9dc3f57137230a016d65bfaee24f8bc5",
"is_verified": false,
"line_number": 1994
},
"line_number": 249
}
],
"tests/conftest.py": [
{
"type": "Base64 High Entropy String",
"filename": "openapis/swagger.yaml",
"hashed_secret": "98c144f5ecbb4dbe575147a39698b6be1a5649dd",
"type": "Private Key",
"filename": "tests/conftest.py",
"hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9",
"is_verified": false,
"line_number": 2041
"line_number": 1176
},
{
"type": "Base64 High Entropy String",
"filename": "openapis/swagger.yaml",
"hashed_secret": "2f58edc671a89190115ecebddf4c70bdd87e3267",
"filename": "tests/conftest.py",
"hashed_secret": "227dea087477346785aefd575f91dd13ab86c108",
"is_verified": false,
"line_number": 2084
"line_number": 1199
}
],
"poetry.lock": [
"tests/credentials/google/test_credentials.py": [
{
"type": "Hex High Entropy String",
"filename": "poetry.lock",
"hashed_secret": "640e60795f08744221f6816fe9dc949c58465256",
"is_verified": false,
"line_number": 1177,
"type": "Private Key"
},
{
"type": "Hex High Entropy String",
"filename": "poetry.lock",
"hashed_secret": "6642e431aaa417100a91214385af6657acb3fab7",
"type": "Secret Keyword",
"filename": "tests/credentials/google/test_credentials.py",
"hashed_secret": "a06bdb09c0106ab559bd6acab2f1935e19f7e939",
"is_verified": false,
"line_number": 1368
"line_number": 381
},
{
"type": "Hex High Entropy String",
"filename": "poetry.lock",
"hashed_secret": "205b95ce89ff252c6045d78ca9d007e73b45dc00",
"is_verified": false,
"line_number": 1200,
"type": "Base64 High Entropy String"
}
],
"tests/conftest.py": [
{
"type": "Private Key",
"filename": "tests/conftest.py",
"hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9",
"type": "Secret Keyword",
"filename": "tests/credentials/google/test_credentials.py",
"hashed_secret": "93aa43c580f5347782e17fba5091f944767b15f0",
"is_verified": false,
"line_number": 1151
"line_number": 474
},
{
"type": "Base64 High Entropy String",
"filename": "tests/conftest.py",
"hashed_secret": "227dea087477346785aefd575f91dd13ab86c108",
"type": "Secret Keyword",
"filename": "tests/credentials/google/test_credentials.py",
"hashed_secret": "768b7fe00de4fd233c0c72375d12f87ce9670144",
"is_verified": false,
"line_number": 1174
"line_number": 476
}
],
"tests/keys/2018-05-01T21:29:02Z/jwt_private_key.pem": [
Expand Down Expand Up @@ -287,7 +253,7 @@
"filename": "tests/scripting/test_fence-create.py",
"hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4",
"is_verified": false,
"line_number": 1117
"line_number": 1120
}
],
"tests/test-fence-config.yaml": [
Expand All @@ -298,13 +264,6 @@
"is_verified": false,
"line_number": 31
},
{
"type": "Secret Keyword",
"filename": "tests/test-fence-config.yaml",
"hashed_secret": "dd29ecf524b030a65261e3059c48ab9e1ecb2585",
"is_verified": false,
"line_number": 85
},
{
"type": "Secret Keyword",
"filename": "tests/test-fence-config.yaml",
Expand All @@ -314,5 +273,5 @@
}
]
},
"generated_at": "2021-05-26T14:24:12Z"
"generated_at": "2021-05-27T15:18:42Z"
}
21 changes: 0 additions & 21 deletions TECHDEBT.md
Original file line number Diff line number Diff line change
@@ -1,22 +1 @@
# Tech debt

### Using 'aud' claim for scopes
- Observed: July 2020
- Impact: (If this tech debt affected your work somehow, add a +1 here with a date and note)
- +1 Zoe 2020 July 15 This is an example of a +1
- +1 Vahid Oct 2020

##### Problem:
Fence puts OAuth2 scopes into the 'aud' claim of access tokens.
##### Why it was done this way:
We don't know.
##### Why this way is problematic:
Per RFC7519 the aud claim [is not meant for scopes](https://tools.ietf.org/html/rfc7519#section-4.1.3).
##### What the solution might be:
GA4GH AAI [already requires](https://github.com/ga4gh/data-security/blob/master/AAI/AAIConnectProfile.md#access_token-issued-by-broker) that a 'scope' claim be included in access tokens issued by Passport Brokers. So as of July 2020 we will put scopes in the 'scope' claim. However, this is in addition to keeping them in the 'aud' claim. Ideally we would only have the scopes in the 'scope' claim.
##### Why we aren't already doing the above:
Fence presently guards several endpoints (e.g. /data, signed urls, SA registration) by checking the scopes in the 'aud' claim of the JWT. This code would need to be changed.
##### Next steps:
Address above.
##### Other notes:
n/a
5 changes: 4 additions & 1 deletion fence/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@ def has_oauth(scope=None):
scope = scope or set()
scope.update({"openid"})
try:
access_token_claims = validate_jwt(aud=scope, purpose="access")
access_token_claims = validate_jwt(
scope=scope,
purpose="access",
)
except JWTError as e:
raise Unauthorized("failed to validate token: {}".format(e))
user_id = access_token_claims["sub"]
Expand Down
10 changes: 5 additions & 5 deletions fence/blueprints/data/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@


@blueprint.route("/<path:file_id>", methods=["DELETE"])
@require_auth_header(aud={"data"})
@require_auth_header(scope={"data"})
@login_required({"data"})
def delete_data_file(file_id):
"""
Expand Down Expand Up @@ -106,7 +106,7 @@ def delete_data_file(file_id):


@blueprint.route("/upload", methods=["POST"])
@require_auth_header(aud={"data"})
@require_auth_header(scope={"data"})
@login_required({"data"})
def upload_data_file():
"""
Expand Down Expand Up @@ -181,7 +181,7 @@ def upload_data_file():


@blueprint.route("/multipart/init", methods=["POST"])
@require_auth_header(aud={"data"})
@require_auth_header(scope={"data"})
@login_required({"data"})
@check_arborist_auth(resource="/data_file", method="file_upload")
def init_multipart_upload():
Expand Down Expand Up @@ -212,7 +212,7 @@ def init_multipart_upload():


@blueprint.route("/multipart/upload", methods=["POST"])
@require_auth_header(aud={"data"})
@require_auth_header(scope={"data"})
@login_required({"data"})
@check_arborist_auth(resource="/data_file", method="file_upload")
def generate_multipart_upload_presigned_url():
Expand Down Expand Up @@ -246,7 +246,7 @@ def generate_multipart_upload_presigned_url():


@blueprint.route("/multipart/complete", methods=["POST"])
@require_auth_header(aud={"data"})
@require_auth_header(scope={"data"})
@login_required({"data"})
@check_arborist_auth(resource="/data_file", method="file_upload")
def complete_multipart_upload():
Expand Down
5 changes: 3 additions & 2 deletions fence/blueprints/data/indexd.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

from fence.auth import (
get_jwt,
has_oauth,
current_token,
login_required,
set_current_token,
Expand Down Expand Up @@ -1148,7 +1147,9 @@ def _get_user_info(sub_to_string=True):
populated information about an anonymous user.
"""
try:
set_current_token(validate_request(aud={"user"}))
set_current_token(
validate_request(scope={"user"}, audience=config.get("BASE_URL"))
)
user_id = current_token["sub"]
if sub_to_string:
user_id = str(user_id)
Expand Down
4 changes: 3 additions & 1 deletion fence/blueprints/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,9 @@ def get(self):
# if we're mocking google auth, mock response to include the email
# from the provided access token
try:
token = validate_request({"user"})
token = validate_request(
scope={"user"}, audience=config.get("BASE_URL")
)
email = get_user_from_claims(token).username
except Exception as exc:
logger.info(
Expand Down
2 changes: 1 addition & 1 deletion fence/blueprints/login/fence_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def get(self):
redirect_uri, **flask.request.args.to_dict()
)
id_token_claims = validate_jwt(
tokens["id_token"], aud={"openid"}, purpose="id", attempt_refresh=True
tokens["id_token"], scope="openid", purpose="id", attempt_refresh=True
)
username = id_token_claims["context"]["user"]["name"]
login_user(
Expand Down
1 change: 1 addition & 0 deletions fence/blueprints/well_known.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def openid_configuration():
"jti",
"auth_time",
"azp",
"scope",
"nonce",
"context",
]
Expand Down
10 changes: 8 additions & 2 deletions fence/jwt/blacklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ def blacklist_encoded_token(encoded_token, public_key=None):
public_key = public_key or keys.default_public_key()
try:
claims = jwt.decode(
encoded_token, public_key, algorithm="RS256", audience="openid"
encoded_token,
public_key,
algorithm="RS256",
options={"verify_aud": False},
)
except jwt.InvalidTokenError as e:
raise BlacklistingError("failed to decode token: {}".format(e))
Expand Down Expand Up @@ -138,7 +141,10 @@ def is_token_blacklisted(encoded_token, public_key=None):
public_key = public_key or keys.default_public_key()
try:
token = jwt.decode(
encoded_token, public_key, algorithm="RS256", audience="openid"
encoded_token,
public_key,
algorithm="RS256",
options={"verify_aud": False},
)
except jwt.exceptions.InvalidTokenError as e:
raise JWTError("could not decode token to check blacklisting: {}".format(e))
Expand Down
Loading