Skip to content

Commit

Permalink
Validation of OIDC claims via JSON schema validator
Browse files Browse the repository at this point in the history
Related: actions/runner#2417 (comment)
Signed-off-by: John Andersen <[email protected]>
  • Loading branch information
pdxjohnny committed Sep 14, 2023
1 parent 7bd5668 commit 215777b
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 5 deletions.
46 changes: 45 additions & 1 deletion .github/workflows/notarize.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,54 @@ jobs:
- name: Submit claim
env:
OIDC_TOKEN: '${{ steps.github-oidc.outputs.token }}'
WORKFLOW_REF: '${{ github.workflow_ref }}'
# Use of job_workflow_sha blocked by
# https://github.com/actions/runner/issues/2417#issuecomment-1718369460
JOB_WORKFLOW_SHA: '${{ github.sha }}'
REPOSITORY_OWNER_ID: '${{ github.repository_owner_id }}'
REPOSITORY_ID: '${{ github.repository_id }}'
run: |
# Create the middleware config file
cat > oidc-middleware-config.json <<EOF
tee oidc-middleware-config.json <<EOF
{
"issuers": ["https://token.actions.githubusercontent.com"],
"claim_schema": {
"https://token.actions.githubusercontent.com": {
"\$schema": "https://json-schema.org/draft/2020-12/schema",
"required": [
"job_workflow_ref",
"job_workflow_sha",
"repository_owner_id",
"repository_id"
],
"properties": {
"job_workflow_ref": {
"type": "string",
"enum": [
"${WORKFLOW_REF}"
]
},
"job_workflow_sha": {
"type": "string",
"enum": [
"${JOB_WORKFLOW_SHA}"
]
},
"repository_owner_id": {
"type": "string",
"enum": [
"${REPOSITORY_OWNER_ID}"
]
},
"repository_id": {
"type": "string",
"enum": [
"${REPOSITORY_ID}"
]
}
}
}
},
"audience": "${SCITT_URL}"
}
EOF
Expand All @@ -79,6 +122,7 @@ jobs:
scitt-emulator server --port 8080 --workspace workspace/ --tree-alg CCF \
--middleware scitt_emulator.oidc:OIDCAuthMiddleware \
--middleware-config-path oidc-middleware-config.json &
sleep 1s
fi
# Submit the claim using OIDC token as auth
scitt-emulator client submit-claim --token "${OIDC_TOKEN}" --url "${SCITT_URL}" --claim claim.cose --out claim.receipt.cbor
5 changes: 4 additions & 1 deletion scitt_emulator/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jwt
import json
import jwcrypto.jwk
import jsonschema
from flask import jsonify
from werkzeug.wrappers import Request
from scitt_emulator.client import HttpClient
Expand All @@ -27,7 +28,9 @@ def __init__(self, app, config_path):

def __call__(self, environ, start_response):
request = Request(environ)
self.validate_token(request.headers["Authorization"].replace("Bearer ", ""))
claims = self.validate_token(request.headers["Authorization"].replace("Bearer ", ""))
if "claim_schema" in self.config and claims["iss"] in self.config["claim_schema"]:
jsonschema.validate(claims, schema=self.config["claim_schema"][claims["iss"]])
return self.app(environ, start_response)

def validate_token(self, token):
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"oidc": [
"PyJWT",
"jwcrypto",
"jsonschema",
]
},
)
39 changes: 36 additions & 3 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ def test_client_cli_token(tmp_path):
key = jwcrypto.jwk.JWK.generate(kty="RSA", size=2048)
algorithm = "RS256"
audience = "scitt.example.org"
subject = "repo:scitt-community/scitt-api-emulator:ref:refs/heads/main"

with Service(
{"key": key, "algorithms": [algorithm]},
Expand All @@ -213,7 +214,21 @@ def test_client_cli_token(tmp_path):
)
middleware_config_path = tmp_path / "oidc-middleware-config.json"
middleware_config_path.write_text(
json.dumps({"issuers": [oidc_service.url], "audience": audience})
json.dumps(
{
"issuers": [oidc_service.url],
"audience": audience,
"claim_schema": {
oidc_service.url: {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": ["sub"],
"properties": {
"sub": {"type": "string", "enum": [subject]},
},
}
},
}
)
)
with Service(
{
Expand Down Expand Up @@ -263,18 +278,36 @@ def test_client_cli_token(tmp_path):
assert not os.path.exists(receipt_path)
assert not os.path.exists(entry_id_path)

# create token
# create token without subject
token = jwt.encode(
{"iss": oidc_service.url, "aud": audience},
key.export_to_pem(private_key=True, password=None),
algorithm=algorithm,
headers={"kid": key.thumbprint()},
)
# submit claim with token
# submit claim with token lacking subject
command += [
"--token",
token,
]
check_error = None
try:
execute_cli(command)
except Exception as error:
check_error = error
assert check_error
assert not os.path.exists(receipt_path)
assert not os.path.exists(entry_id_path)

# create token with subject
token = jwt.encode(
{"iss": oidc_service.url, "aud": audience, "sub": subject},
key.export_to_pem(private_key=True, password=None),
algorithm=algorithm,
headers={"kid": key.thumbprint()},
)
# submit claim with token containing subject
command[-1] = token
execute_cli(command)
assert os.path.exists(receipt_path)
assert os.path.exists(entry_id_path)

0 comments on commit 215777b

Please sign in to comment.