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

Fix and test adding new issuers #260

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3659f05
fix add issuer and new tests
beejones Dec 11, 2024
e2539f4
fixing auth tests
beejones Dec 11, 2024
37618bc
fix test_auth
beejones Dec 12, 2024
a2359f2
remove user certs
beejones Dec 13, 2024
92020eb
Fixing env vars
beejones Dec 13, 2024
9497639
Try to fix aci failing
beejones Dec 13, 2024
9c2cf3e
fix aci failing
beejones Dec 13, 2024
fa65601
Adding env to jwt_issuer up
beejones Dec 13, 2024
ab9f92b
add env vars back into aci script
beejones Dec 13, 2024
d039514
add unset
beejones Dec 13, 2024
824775c
Merge remote-tracking branch 'origin/main' into beejones/fix-jwt-issuers
DomAyre Dec 17, 2024
d3a2843
Minor layout changes
DomAyre Dec 17, 2024
e7cd184
Recreate the JWT Issuer for each test
DomAyre Dec 17, 2024
4252e2f
.
DomAyre Dec 17, 2024
685aad3
Merge branch 'main' into beejones/fix-jwt-issuers
beejones Jan 7, 2025
a8192dc
Merge main
beejones Jan 8, 2025
3a79912
fix build error
beejones Jan 8, 2025
9c572f3
Fix system test failing
beejones Jan 8, 2025
08e69e9
Merge branch 'main' into beejones/fix-jwt-issuers
beejones Jan 8, 2025
d93bd4c
debug js_app_set.sh
beejones Jan 8, 2025
c78a9c6
Merge branch 'beejones/fix-jwt-issuers' of https://github.com/microso…
beejones Jan 8, 2025
e70301a
update yanked package
beejones Jan 8, 2025
68b9668
dummy change
beejones Jan 8, 2025
34b7d0b
sync aci cleanroom scripts with main
beejones Jan 9, 2025
d932624
temporary create directory to see if this fixes the aci cleanroom ci …
beejones Jan 9, 2025
d74e9b3
remove debugging flag in js_app_set.sh
beejones Jan 9, 2025
6bc2264
cleanup
beejones Jan 9, 2025
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
2 changes: 1 addition & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@
"openapi": {
"responses": {
"200": {
"description": "Generate a heartbeat response"
"description": "Generate an auth response"
}
}
}
Expand Down
64 changes: 32 additions & 32 deletions governance/proposals/set_jwt_issuer.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
{
"actions": [
{
"name": "set_jwt_issuer",
"args": {
"issuer": "http://Demo-jwt-issuer",
"key_filter": "all",
"jwks": {
"keys": [
${JWK}
]
}
"actions": [
{
"name": "set_jwt_issuer",
"args": {
"issuer": "${JWT_TOKEN_ISSUER_URL}",
"key_filter": "all",
"jwks": {
"keys": [
${JWK}
]
}
},
{
"name": "set_jwt_validation_policy",
"args": {
"issuer": "http://Demo-jwt-issuer",
"validation_policy": {
"iss": "http://Demo-jwt-issuer",
"sub": "c0d8e9a7-6b8e-4e1f-9e4a-3b2c1d0f5a6b",
"name": "Cool caller"
}
}
},
{
"name": "set_jwt_validation_policy",
"args": {
"issuer": "${JWT_TOKEN_ISSUER_URL}",
"validation_policy": {
"iss": "${JWT_TOKEN_ISSUER_URL}",
"sub": "c0d8e9a7-6b8e-4e1f-9e4a-3b2c1d0f5a6b",
"name": "Cool caller"
}
},
{
"name": "set_jwt_public_signing_keys",
"args": {
"issuer": "http://Demo-jwt-issuer",
"jwks": {
"keys": [
${JWK}
]
}
}
},
{
"name": "set_jwt_public_signing_keys",
"args": {
"issuer": "${JWT_TOKEN_ISSUER_URL}",
"jwks": {
"keys": [
${JWK}
]
}
}
]
}
]
}
2 changes: 2 additions & 0 deletions scripts/ccf/az-cleanroom-aci/down.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ az-cleanroom-aci-down() {
unset KMS_SERVICE_CERT_PATH
unset KMS_MEMBER_CERT_PATH
unset KMS_MEMBER_PRIVK_PATH
unset KMS_USER_CERT_PATH
unset KMS_USER_PRIVK_PATH

set +e
}
Expand Down
10 changes: 9 additions & 1 deletion scripts/ccf/az-cleanroom-aci/up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ az-cleanroom-aci-up() {
export KMS_SERVICE_CERT_PATH="$WORKSPACE/service_cert.pem"
export KMS_MEMBER_CERT_PATH="$WORKSPACE/ccf-operator_cert.pem"
export KMS_MEMBER_PRIVK_PATH="$WORKSPACE/ccf-operator_privk.pem"
export KMS_USER_CERT_PATH="$WORKSPACE/sandbox_common/user0_cert.pem"
export KMS_USER_PRIVK_PATH="$WORKSPACE/sandbox_common/user0_privk.pem"
export JWT_TOKEN_ISSUER_URL="http://localhost:3000/token"
DomAyre marked this conversation as resolved.
Show resolved Hide resolved
export JWT_ISSUER="http://Demo-jwt-issuer"

sudo cp $KMS_SERVICE_CERT_PATH /usr/local/share/ca-certificates/kms_ca.crt
sudo update-ca-certificates
Expand All @@ -55,5 +59,9 @@ jq -n '{
KMS_URL: env.KMS_URL,
KMS_SERVICE_CERT_PATH: env.KMS_SERVICE_CERT_PATH,
KMS_MEMBER_CERT_PATH: env.KMS_MEMBER_CERT_PATH,
KMS_MEMBER_PRIVK_PATH: env.KMS_MEMBER_PRIVK_PATH
KMS_MEMBER_PRIVK_PATH: env.KMS_MEMBER_PRIVK_PATH,
KMS_USER_CERT_PATH: env.KMS_USER_CERT_PATH,
KMS_USER_PRIVK_PATH: env.KMS_USER_PRIVK_PATH,
JWT_TOKEN_ISSUER_URL: env.JWT_TOKEN_ISSUER_URL,
JWT_ISSUER: env.JWT_ISSUER,
}'
2 changes: 2 additions & 0 deletions scripts/ccf/sandbox_local/down.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ ccf-sandbox-local-down() {
unset KMS_SERVICE_CERT_PATH
unset KMS_MEMBER_CERT_PATH
unset KMS_MEMBER_PRIVK_PATH
unset KMS_USER_CERT_PATH
unset KMS_USER_PRIVK_PATH

set +e
}
Expand Down
11 changes: 10 additions & 1 deletion scripts/ccf/sandbox_local/up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ ccf-sandbox-local-up() {
export KMS_SERVICE_CERT_PATH="$WORKSPACE/sandbox_common/service_cert.pem"
export KMS_MEMBER_CERT_PATH="$WORKSPACE/sandbox_common/member0_cert.pem"
export KMS_MEMBER_PRIVK_PATH="$WORKSPACE/sandbox_common/member0_privk.pem"
export KMS_USER_CERT_PATH="$WORKSPACE/sandbox_common/user0_cert.pem"
export KMS_USER_PRIVK_PATH="$WORKSPACE/sandbox_common/user0_privk.pem"
export JWT_TOKEN_ISSUER_URL="http://localhost:3000/token"
DomAyre marked this conversation as resolved.
Show resolved Hide resolved
export JWT_ISSUER="http://Demo-jwt-issuer"

set +e
}
Expand All @@ -29,5 +33,10 @@ jq -n '{
KMS_URL: env.KMS_URL,
KMS_SERVICE_CERT_PATH: env.KMS_SERVICE_CERT_PATH,
KMS_MEMBER_CERT_PATH: env.KMS_MEMBER_CERT_PATH,
KMS_MEMBER_PRIVK_PATH: env.KMS_MEMBER_PRIVK_PATH
KMS_MEMBER_PRIVK_PATH: env.KMS_MEMBER_PRIVK_PATH,
KMS_USER_CERT_PATH: env.KMS_USER_CERT_PATH,
KMS_USER_PRIVK_PATH: env.KMS_USER_PRIVK_PATH,
JWT_TOKEN_ISSUER_URL: env.JWT_TOKEN_ISSUER_URL,
JWT_ISSUER: env.JWT_ISSUER,

}'
5 changes: 3 additions & 2 deletions scripts/jwt_issuer/up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ jwt-issuer-up() {

sudo chown $USER:$USER -R $JWT_ISSUER_WORKSPACE

export JWT_ISSUER="http://localhost:3000/token"
export JWT_TOKEN_ISSUER_URL="http://localhost:3000/token"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the difference between these two things? They're both URLs and I presume they both issue tokens so maybe we can think of a better naming scheme?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not super keen on JWT_TOKEN because the T in JWT stands for token.

So we have JSON Web Token Token Issuer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT_ISSUER is the name used in CCF for registering the JWT issuer.
JWT_TOKEN_ISSUER_URL is the actual url of the endpoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is very common to talk about JWT tokens. Just do a search for "JWT token".

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT_ISSUER is the name used in CCF for registering the JWT issuer. JWT_TOKEN_ISSUER_URL is the actual url of the endpoint.

Ah thanks for the explanation, that makes sense

it is very common to talk about JWT tokens. Just do a search for "JWT token".

Being common doesn't make it correct or less redundant, and I wouldn't claim it's more common that just JWT, jwt.io always just says JWT.

How about we use

  • JWT_ISSUER_URL which is the URL which issues JWTs
  • JWT_ISSUER_NAME which is the name of the thing which issues JWTs (for CCF)
    • We could make it clearly a name by making it not look like a URL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe CCF requires this to be a url. Need to check this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it needs to be a URL, could it just be the actual URL so we only need one variable instead of two?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed.
{
bool validate_issuer(
const std::string& iss,
const std::optionalstd::string& tid,
std::string constraint)
{
LOG_DEBUG_FMT(
"Verify token.iss {} and token.tid {} against published key issuer {}",
iss,
tid,
constraint);

const auto issuer_url = ::http::parse_url_full(constraint);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it needs to be a URL, could it just be the actual URL so we only need one variable instead of two?

See #260 (comment)

Copy link
Collaborator

@DomAyre DomAyre Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand it's a test JWT issuer, but why can't both values be http://localhost:3000/token? In which case the JWT issuer up script only needs to emit one value not two? Isn't it also kind of wrong to have a pretend URL for the issuer which doesn't match it's actual URL?

I've tested this locally with a couple of small changes to the issuer and it works fine

export JWT_ISSUER="http://Demo-jwt-issuer"

set +e
}
Expand All @@ -23,5 +24,5 @@ jwt-issuer-up

jq -n '{
JWT_ISSUER_WORKSPACE: env.JWT_ISSUER_WORKSPACE,
JWT_ISSUER: env.JWT_ISSUER,
DomAyre marked this conversation as resolved.
Show resolved Hide resolved
JWT_TOKEN_ISSUER_URL: env.JWT_TOKEN_ISSUER_URL,
}'
50 changes: 50 additions & 0 deletions scripts/kms/endpoints/auth.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash

# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

auth() {
params=()
auth="jwt"
jwtprops=""

# Parse command-line arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--auth)
auth="$2"
shift 2
;;
--jwtprops)
jwtprops="$2"
shift 2
;;
*)
echo "Unknown parameter: $1"
exit 1
;;
esac
done

# Extract iss from jwtprops if present
if [[ "$jwtprops" == *"iss="* ]]; then
export JWT_TOKEN_ISSUER_URL=$(echo "$jwtprops" | grep -oP '(?<=iss=)[^&]*')
fi

auth_arg=()
if [[ "$auth" == "member_cert" ]]; then
auth_arg=(--cert $KMS_MEMBER_CERT_PATH --key $KMS_MEMBER_PRIVK_PATH)
elif [[ "$auth" == "user_cert" ]]; then
auth_arg=(--cert $KMS_USER_CERT_PATH --key $KMS_USER_PRIVK_PATH)
elif [[ "$auth" == "jwt" ]]; then
auth_arg=(-H "Authorization: Bearer $(curl -X POST $JWT_TOKEN_ISSUER_URL$jwtprops | jq -r '.access_token')")
fi

curl $KMS_URL/app/auth \
--cacert $KMS_SERVICE_CERT_PATH \
"${auth_arg[@]}" \
-H "Content-Type: application/json" \
-w '\n%{http_code}\n'
}

auth "$@"
2 changes: 1 addition & 1 deletion scripts/kms/endpoints/key.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ key() {
if [[ "$auth" == "member_cert" ]]; then
auth_arg=(--cert $KMS_MEMBER_CERT_PATH --key $KMS_MEMBER_PRIVK_PATH)
elif [[ "$auth" == "jwt" ]]; then
auth_arg=(-H "Authorization: Bearer $(curl -X POST $JWT_ISSUER | jq -r '.access_token')")
auth_arg=(-H "Authorization: Bearer $(curl -X POST $JWT_TOKEN_ISSUER_URL | jq -r '.access_token')")
fi

curl $KMS_URL/app/key${query_string} \
Expand Down
18 changes: 18 additions & 0 deletions scripts/kms/jwt_issuer_trust.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ jwt-issuer-trust() {

REPO_ROOT="$(realpath "$(dirname "$(realpath "${BASH_SOURCE[0]}")")/../..")"

# Check for new issuer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my own understanding, the scheme is:

  • If you call this without --iss it will use JWT_TOKEN_ISSUER_URL which will have been set if you bring up a local test JWT issuer
  • If you have some external JWT issuer you can just specify it with this --iss flag?

If my understanding is correct I really like this scheme!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an external one we need to set JWT_TOKEN_ISSUER_URL to the token endpoint.
--iss will the name for the iss property in the token. CCF uses this to register the issuer.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is it equivalent to setting JWT_ISSUER? In which case why are we mixing methods? I think we either allow fully specifying which JWT issuer we're trusting via command line params or we just require the env variables to be changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test adding a new issuer implemented by our test token issuer.
For this case I needed to specify the issuer JWT_ISSUER and keep the url to the test token url.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that addresses my point about mixed methods, for the test you could set both env variables for the test, or you could provider command line params for both and specify them for the test?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we've decided to unify JWT_ISSUER this goes away

while [[ $# -gt 0 ]]; do
case "$1" in
--iss)
JWT_TOKEN_ISSUER_URL="$2"
shift 2
;;
--iss=*)
JWT_TOKEN_ISSUER_URL="${1#*=}"
shift 1
;;
*)
echo "Unknown parameter: $1"
exit 1
;;
esac
done

# Populate the JWK of the token issuer
PRIVATE_PEM="$JWT_ISSUER_WORKSPACE/private.pem"
CERT_PEM="$JWT_ISSUER_WORKSPACE/cert.pem"
Expand Down
41 changes: 0 additions & 41 deletions src/authorization/jwt/DemoJwtProvider.ts

This file was deleted.

14 changes: 7 additions & 7 deletions src/authorization/jwt/JwtValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { ServiceResult } from "../../utils/ServiceResult";
import { IValidatorService } from "../IValidationService";
import { JwtIdentityProviderEnum } from "./JwtIdentityProviderEnum";
import { IJwtIdentityProvider } from "./IJwtIdentityProvider";
import { DemoJwtProvider } from "./DemoJwtProvider";
import { MsJwtProvider } from "./MsJwtProvider";
import { Logger, LogContext } from "../../utils/Logger";

Expand All @@ -23,24 +22,25 @@ export class JwtValidator implements IValidatorService {
JwtIdentityProviderEnum.MS_AAD,
new MsJwtProvider("JwtProvider", this.logContext),
);
this.identityProviders.set(
JwtIdentityProviderEnum.Demo,
new DemoJwtProvider("DemoJwtProvider"),
);
}

/*
* Validate the request
* @param request request to validate with JWT
* */
validate(request: ccfapp.Request<any>): ServiceResult<string> {
const jwtCaller = request.caller as unknown as ccfapp.JwtAuthnIdentity;
Logger.debug(
`Authorization: JWT jwtCaller (JwtValidator)-> ${<JwtIdentityProviderEnum>jwtCaller.jwt.keyIssuer}`,
this.logContext
);
Logger.info(`JWT content: ${JSON.stringify(jwtCaller.jwt)}`, this.logContext);
const provider = this.identityProviders.get(
<JwtIdentityProviderEnum>jwtCaller.jwt.keyIssuer,
JwtIdentityProviderEnum.MS_AAD
);

if (!provider) {
const error = `Authorization: JWT validation provider is undefined (JwtValidator) for ${<JwtIdentityProviderEnum>jwtCaller.jwt.keyIssuer}`;
const error = `Authorization: JWT validation provider ${JwtIdentityProviderEnum.MS_AAD} is undefined (JwtValidator) for ${<JwtIdentityProviderEnum>jwtCaller.jwt.keyIssuer}`;
Logger.error(error, this.logContext);
return ServiceResult.Failed(
{ errorMessage: error, errorType: "caller error" },
Expand Down
2 changes: 1 addition & 1 deletion src/authorization/jwt/MsJwtProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const authorizeJwt = (
errorMessage,
errorType: "AuthenticationError",
},
500,
401,
logContext
);
}
Expand Down
4 changes: 4 additions & 0 deletions test/system-test/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ def keyReleasePolicy(**kwargs):

def settingsPolicy(**kwargs):
return call_endpoint("settingsPolicy", **kwargs)


def auth(**kwargs):
return call_endpoint("auth", **kwargs)
47 changes: 47 additions & 0 deletions test/system-test/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from endpoints import auth
from utils import apply_kms_constitution, trust_jwt_issuer


def test_auth_member_cert(setup_kms):
status_code, auth_json = auth(auth="member_cert")
print(auth_json)
beejones marked this conversation as resolved.
Show resolved Hide resolved
assert status_code == 200
assert auth_json["auth"]["policy"] == "member_cert"


def test_auth_user_cert(setup_kms):
status_code, auth_json = auth(auth="user_cert")
print(auth_json)
assert status_code == 200
assert auth_json["auth"]["policy"] == "user_cert"


def test_auth_jwt(setup_kms, setup_jwt_issuer):
apply_kms_constitution()
trust_jwt_issuer()
status_code, auth_json = auth(auth="jwt")
print(auth_json)
assert status_code == 200
assert auth_json["auth"]["policy"] == "jwt"


def test_auth_jwt_new_issuer(setup_kms, setup_jwt_issuer):
issuer = "https://new-issuer"
apply_kms_constitution()
trust_jwt_issuer(iss=issuer)
status_code, auth_json = auth(auth="jwt", jwtprops=f"?iss={issuer}")
print(auth_json)
assert status_code == 200


def test_auth_jwt_wrong_iss(setup_kms, setup_jwt_issuer):
apply_kms_constitution()
trust_jwt_issuer()
status_code, auth_json = auth(auth="jwt", jwtprops="?iss=test")
print(auth_json)
assert status_code == 401


if __name__ == "__main__":
import pytest
pytest.main([__file__, "-s"])
Loading
Loading