-
Notifications
You must be signed in to change notification settings - Fork 983
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
Models, routes and views for creating OIDC publishers #10753
Merged
Merged
Changes from 11 commits
Commits
Show all changes
93 commits
Select commit
Hold shift + click to select a range
48ab12e
warehouse/oidc: rough model skeleton
woodruffw 7db2c2c
warehouse/oidc: fix imports
woodruffw eab5e70
warehouse/migrations: add migration for OIDC models
woodruffw 09d0966
warehouse/migrations: reformat
woodruffw 24b9eab
warehouse/oidc: add basic verification logic
woodruffw c56476f
oidc/services: reduce clock skew leeway to 30s
woodruffw 41a1ca0
warehouse/oidc: refactor claim verification
woodruffw 63d16a2
oidc/models: fill in missing properties
woodruffw 50549ec
warehouse/migrations: remove original OIDC migration
woodruffw f1d7162
warehouse: add OIDC migration, fix association
woodruffw 5479c81
warehouse: reformat
woodruffw 1e0f26c
warehouse: OIDC route/view skeleton work
woodruffw bf5859b
warehouse: form, view logic for adding OIDC providers
woodruffw a62a917
manage/views: disable HTTP cache, add TODO
woodruffw ed1559c
warehouse: move oidc views to "publishing"
woodruffw 971ccf2
warehouse: provider deletion routing
woodruffw 4aad8ee
warehouse: shore up constraints, better error flashes
woodruffw ac48e82
Merge branch 'main' into tob-oidc-db-models
woodruffw ba1a39d
Merge branch 'main' into tob-oidc-db-models
woodruffw 100120c
warehouse/migrations: rebase revision
woodruffw 1ca025a
warehouse/templates: update OIDC language
woodruffw d54dd38
warehouse: OIDC rate limiting groundwork
woodruffw c700c92
manage/views: clean up OIDC events
woodruffw d215931
warehouse: use GitHub token for API requests, when available
woodruffw 232ae6f
oidc/forms: special casing for rate limiting
woodruffw 62a795f
warehouse: split user/repo form inputs apart
woodruffw 5ed70bc
warehouse/templates: link to GitHub's OIDC docs
woodruffw 2c9722d
oidc/models: remove actor from checked claims
woodruffw be82d1b
templates/email: add OIDC email templates
woodruffw 6c00487
warehouse: fix templates, add email sending logic
woodruffw a6aa4a0
warehouse: add an AdminFlag for OIDC control
woodruffw e259dc8
oidc/models: use set operators
woodruffw d0b37d2
oidc/forms: exception driven handling for GitHub API errors
woodruffw 1fc826a
warehouse: OIDC ratelimiting logic
woodruffw ae1f6bb
Merge branch 'main' into tob-oidc-db-models
woodruffw f307de7
warehouse/locale: update translations
woodruffw bd568c8
Merge remote-tracking branch 'upstream/main' into tob-oidc-db-models
woodruffw 5bfba05
Merge branch 'main' into tob-oidc-db-models
woodruffw 67fb78c
warehouse: lintage
woodruffw d249c6b
templates/manage/settings: remove vestigial HTML
woodruffw 07a7119
warehouse: address feedback
woodruffw 788ddfa
manage/views: more feedback addressing
woodruffw 77c30d9
Update warehouse/manage/views.py
woodruffw 7c6a293
manage/views: fixups
woodruffw b864d94
warehouse: add "OIDC provider removed" emails
woodruffw f726919
oidc/forms: use GH org regex in callable validator body
woodruffw 901615a
Merge remote-tracking branch 'upstream/main' into tob-oidc-db-models
woodruffw 63caa2a
warehouse/locale: update translations
woodruffw 69ba7db
tests, warehouse: begin writing unit tests
woodruffw c72357b
More tests, restructure for testing
woodruffw 45e2721
tests: fill in GitHubProviderForm tests
woodruffw f6fde8d
tests, warehouse: more tests, adaptations for testing
woodruffw 17b2473
tests: more manage/view tests
woodruffw 6d3130b
tests, warehouse: ratelimit tests, fix bug
woodruffw 8bd4d04
tests: round out ratelimiting
woodruffw e8f1a8d
tests: more tests
woodruffw d12c66e
Merge remote-tracking branch 'upstream/main' into tob-oidc-db-models
woodruffw ce65932
tests, warehouse: OIDC deletion tests
woodruffw 792b306
tests, warehouse: fill in model checks
woodruffw 2215f39
oidc/models: type hints
woodruffw 1a21b4f
warehouse/locale: `make translations`
woodruffw 7bb51bb
Merge branch 'main' into tob-oidc-db-models
woodruffw cc0c21a
tests, warehouse: site-wide OIDC feature flag
woodruffw 3fa705e
warehouse: `make translations`
woodruffw c2ca980
treewide: route to 404 when OIDC is disabled
woodruffw 9c459d6
Merge remote-tracking branch 'upstream/main' into tob-oidc-db-models
woodruffw f6b2cf0
warehouse: `make translations`
woodruffw 8912a40
Update warehouse/templates/manage/publishing.html
woodruffw a9a9175
Merge branch 'main' into tob-oidc-db-models
woodruffw 7482d78
oidc/{interfaces,services}: simplify API
woodruffw 17eae83
tests: update
woodruffw c94b2b8
warehouse/migrations: rebase
woodruffw 1d4e12b
tests, warehouse: move ratelimit hit up
woodruffw 41f4426
Merge branch 'main' into tob-oidc-db-models
woodruffw 7e380c9
warehouse: `make translations`
woodruffw 5dafd98
warehouse: plug in more OIDC metrics
woodruffw 0ff3f01
warehouse/oidc: add a `verify_for_helper` iface method
woodruffw c945ee7
manage/views: add provider names to metrics
woodruffw 32cfd38
oidc/services: add project tag to metrics during JWT verification
woodruffw 63bce66
oidc/services: include provider name in metrics too
woodruffw b677a8d
tests/unit: plumb metrics through OIDC unit tests
woodruffw f9813ad
tests/unit: fill in coverage
woodruffw dd95500
warehouse: `make translations`
woodruffw 8df5ccd
Merge branch 'main' into tob-oidc-db-models
woodruffw c9705fe
Merge branch 'main' into tob-oidc-db-models
woodruffw 04c7261
tests, warehouse: disable `job_workflow_ref`
woodruffw de52f9f
Merge branch 'main' into tob-oidc-db-models
woodruffw 4609559
Apply suggestions from code review
woodruffw 52c4e15
tests, warehouse: update tests for changes
woodruffw 73eef39
warehouse, tests: email all users on OIDC changes
woodruffw 4651ce8
warehouse, tests: include publisher info in OIDC emails
woodruffw e090ad3
warehouse: `make translations`
woodruffw eeb599d
Merge branch 'main' into tob-oidc-db-models
di File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
92 changes: 92 additions & 0 deletions
92
warehouse/migrations/versions/f345394c444f_add_initial_oidc_provider_models.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
""" | ||
Add initial OIDC provider models | ||
|
||
Revision ID: f345394c444f | ||
Revises: 29a8901a4635 | ||
Create Date: 2022-02-15 21:11:41.693791 | ||
""" | ||
|
||
import sqlalchemy as sa | ||
|
||
from alembic import op | ||
from sqlalchemy.dialects import postgresql | ||
|
||
revision = "f345394c444f" | ||
down_revision = "29a8901a4635" | ||
|
||
# Note: It is VERY important to ensure that a migration does not lock for a | ||
# long period of time and to ensure that each individual migration does | ||
# not break compatibility with the *previous* version of the code base. | ||
# This is because the migrations will be ran automatically as part of the | ||
# deployment process, but while the previous version of the code is still | ||
# up and running. Thus backwards incompatible changes must be broken up | ||
# over multiple migrations inside of multiple pull requests in order to | ||
# phase them in over multiple deploys. | ||
|
||
|
||
def upgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.create_table( | ||
"oidc_providers", | ||
sa.Column( | ||
"id", | ||
postgresql.UUID(as_uuid=True), | ||
server_default=sa.text("gen_random_uuid()"), | ||
nullable=False, | ||
), | ||
sa.Column("discriminator", sa.String(), nullable=True), | ||
sa.PrimaryKeyConstraint("id"), | ||
) | ||
op.create_table( | ||
"github_oidc_providers", | ||
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False), | ||
sa.Column("repository_name", sa.String(), nullable=True), | ||
sa.Column("owner", sa.String(), nullable=True), | ||
sa.Column("owner_id", sa.String(), nullable=True), | ||
sa.Column("workflow_name", sa.String(), nullable=True), | ||
sa.ForeignKeyConstraint( | ||
["id"], | ||
["oidc_providers.id"], | ||
), | ||
sa.PrimaryKeyConstraint("id"), | ||
) | ||
op.create_table( | ||
"oidc_provider_project_association", | ||
sa.Column( | ||
"id", | ||
postgresql.UUID(as_uuid=True), | ||
server_default=sa.text("gen_random_uuid()"), | ||
nullable=False, | ||
), | ||
sa.Column("oidc_provider_id", postgresql.UUID(as_uuid=True), nullable=False), | ||
sa.Column("project_id", postgresql.UUID(as_uuid=True), nullable=False), | ||
sa.ForeignKeyConstraint( | ||
["oidc_provider_id"], | ||
["oidc_providers.id"], | ||
), | ||
sa.ForeignKeyConstraint( | ||
["project_id"], | ||
["projects.id"], | ||
), | ||
sa.PrimaryKeyConstraint("id", "oidc_provider_id", "project_id"), | ||
) | ||
# ### end Alembic commands ### | ||
|
||
|
||
def downgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.drop_table("oidc_provider_project_association") | ||
op.drop_table("github_oidc_providers") | ||
op.drop_table("oidc_providers") | ||
# ### end Alembic commands ### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import sentry_sdk | ||
|
||
from sqlalchemy import Column, ForeignKey, String, orm | ||
from sqlalchemy.dialects.postgresql import UUID | ||
|
||
from warehouse import db | ||
from warehouse.packaging.models import Project | ||
|
||
|
||
class OIDCProviderProjectAssociation(db.Model): | ||
__tablename__ = "oidc_provider_project_association" | ||
|
||
oidc_provider_id = Column( | ||
UUID(as_uuid=True), | ||
ForeignKey("oidc_providers.id"), | ||
nullable=False, | ||
primary_key=True, | ||
) | ||
project_id = Column( | ||
UUID(as_uuid=True), ForeignKey("projects.id"), nullable=False, primary_key=True | ||
) | ||
|
||
|
||
class OIDCProvider(db.Model): | ||
__tablename__ = "oidc_providers" | ||
|
||
discriminator = Column(String) | ||
projects = orm.relationship( | ||
Project, | ||
secondary=OIDCProviderProjectAssociation.__table__, | ||
backref="oidc_providers", | ||
) | ||
|
||
__mapper_args__ = {"polymorphic_on": discriminator} | ||
|
||
# A map of claim names to "check" functions, each of which | ||
# has the signature `check(ground-truth, signed-claim) -> bool`. | ||
__verifiable_claims__ = dict() | ||
|
||
# Claims that have already been verified during the JWT signature | ||
# verification phase. | ||
__preverified_claims__ = { | ||
"iss", | ||
"iat", | ||
"nbf", | ||
"exp", | ||
"aud", | ||
} | ||
|
||
# Individual providers should explicitly override this set, | ||
# indicating any custom claims that are known to be present but are | ||
# not checked as part of verifying the JWT. | ||
__unchecked_claims__ = set() | ||
|
||
def verify_claims(self, signed_claims): | ||
""" | ||
Given a JWT that has been successfully decoded (checked for a valid | ||
signature and basic claims), verify it against the more specific | ||
claims of this provider. | ||
""" | ||
|
||
# Defensive programming: treat the absence of any claims to verify | ||
# as a failure rather than trivially valid. | ||
if not self.__verifiable_claims__: | ||
return False | ||
|
||
# All claims should be accounted for. | ||
# The presence of an unaccounted claim is not an error, only a warning | ||
# that the JWT payload has changed. | ||
known_claims = self.__verifiable_claims__.keys().union( | ||
self.__preverified_claims__, self.__unchecked_claims__ | ||
) | ||
unaccounted_claims = known_claims.difference(signed_claims.keys()) | ||
woodruffw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if unaccounted_claims: | ||
sentry_sdk.capture_message( | ||
f"JWT for {self.__class__.__name__} has unaccounted claims: {unaccounted_claims}" | ||
) | ||
|
||
# Finally, perform the actual claim verification. | ||
for claim_name, check in self.__verifiable_claims__.items(): | ||
if not check(self.getattr(claim_name), signed_claims[claim_name]): | ||
return False | ||
|
||
return True | ||
|
||
|
||
class GitHubProvider(OIDCProvider): | ||
__tablename__ = "github_oidc_providers" | ||
__mapper_args__ = {"polymorphic_identity": "GitHubProvider"} | ||
|
||
id = Column(UUID(as_uuid=True), ForeignKey(OIDCProvider.id), primary_key=True) | ||
repository_name = Column(String) | ||
owner = Column(String) | ||
owner_id = Column(String) | ||
workflow_name = Column(String) | ||
|
||
__verifiable_claims__ = { | ||
"repository": str.__eq__, | ||
"job_workflow_ref": str.startswith, | ||
woodruffw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"actor": str.__eq__, | ||
"workflow": str.__eq__, | ||
} | ||
|
||
__unchecked_claims__ = { | ||
"jti", | ||
"sub", | ||
"ref", | ||
"sha", | ||
"run_id", | ||
"run_number", | ||
"run_attempt", | ||
"head_ref", | ||
"base_ref", | ||
"event_name", | ||
"ref_type", | ||
} | ||
|
||
@property | ||
def repository(self): | ||
return f"{self.owner}/{self.repository_name}" | ||
|
||
@property | ||
def job_workflow_ref(self): | ||
return f"{self.repository}/.github/workflows/{self.workflow_name}.yml" | ||
|
||
@property | ||
def actor(self): | ||
return self.owner | ||
woodruffw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@property | ||
def workflow(self): | ||
return self.workflow_name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
N.B.: Let me know if there's a better way to do this many-many mapping. This is the pattern I'm familiar with, but there are probably others.