Skip to content

Authenticate an exchanged token#2063

Closed
lei-tang wants to merge 12 commits intoistio:masterfrom
lei-tang:merge-authn-exchanged-token
Closed

Authenticate an exchanged token#2063
lei-tang wants to merge 12 commits intoistio:masterfrom
lei-tang:merge-authn-exchanged-token

Conversation

@lei-tang
Copy link
Copy Markdown
Contributor

What this PR does / why we need it: authenticate an exchanged token.

Which issue this PR fixes (optional, in fixes #<issue number>(, fixes #<issue_number>, ...) format, will close that issue when PR gets merged): fixes #2062

Special notes for your reviewer:

Release note:

@googlebot googlebot added the cla: yes Set by the Google CLA bot to indicate the author of a PR has signed the Google CLA. label Dec 13, 2018
// The JWT issuer key name
static const std::string kJwtIssuerKey = "iss";
// The APToken issuer
static const std::string kAPTokenIssuer = "https://cloud.google.com/iap";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

remove this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

@lei-tang lei-tang force-pushed the merge-authn-exchanged-token branch from 7b3d487 to bb55390 Compare December 13, 2018 05:11
return AuthnUtils::ProcessJwtPayload(jwt_payload, payload->mutable_jwt());
std::string payload_to_process = jwt_payload;
std::string original_payload;
if (AuthnUtils::ExtractOriginalPayload(jwt_payload, &original_payload)) {
Copy link
Copy Markdown
Contributor

@quanjielin quanjielin Dec 13, 2018

Choose a reason for hiding this comment

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

we may require token exchange service to keep origin header of jwt, so that filter could restore original jwt payload (for back compatibility in case app process original jwt payload); this could be done later. /cc @diemtvu

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is the goal making AP token "transparent" to the service admin? I don't think this is right way of handling ap token. The service/namespace admin should be well aware that he/she is handling AP token, which is different from an external token.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agree with Limin. This should be configured explicitly. There could be use cases that admin want to keep the aptoken credential, or it can pack original payload differently (which we may need more than just a boolean flag).

I would suggest adding a flag to JWT configuration, and calling this function base on that value.

Regarding sending the original payload to backend, it's up to our decision on the feature. If we want user can use IAP without any change on there application, then it would be a nice option to have, though I think it would be more complicated. We can ignore that for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Based on the discussion today, the code has been revised.

@@ -0,0 +1 @@
eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJleGFtcGxlLWF1ZGllbmNlIiwiZW1haWwiOiJmb29AZ29vZ2xlLmNvbSIsImV4cCI6NDY5ODAyMzM3NywiaWF0IjoxNTQ0NDIzMzc3LCJpc3MiOiJodHRwczovL2Nsb3VkLmdvb2dsZS5jb20vaWFwIiwiaXN0aW9fYXR0cmlidXRlcyI6W3sic291cmNlLmlwIjoiMTI3LjAuMC4xIn1dLCJrZXkxIjpbInZhbDIiLCJ2YWwzIl0sIm9yaWdpbmFsX3BheWxvYWQiOnsiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5leGFtcGxlLmNvbSIsInN1YiI6ImV4YW1wbGUtc3ViamVjdCJ9LCJzdWIiOiJodHRwczovL2FjY291bnRzLmV4YW1wbGUuY29tLzEyMzQ1NTY3ODkwIn0.lY-bObNybFN8RxguZcgEd3TrC7-dSRH6VjXqkMvaN1WXz5xk8EEca0vlfkc2RRCZCCGDsSJiZOWOkV5c3RX5EAGcHppMFxuX2YZMQOoConuN_WCdp88gTKuICDUp_ZLi06nWOzqhIr4GG2QYDxSOMxTFqMl_qcJTKEcjpJM-pbhVTgbTb020C4_mFOEespff2Kh_ySzkZnlreddP_oUvUrKKoIHBf3M_ZKpivuSyyBjL52iFmsMJL4nmsk-xLIDuw5NeNyUGDa5j-EwiD3XvrFurrIAkKPT42E5Z5uomuw8h-zUoJt_jpdIynOOA2eyQXfXwsVnpOnMWDut66u92xQ
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

when have time, could you pls add a short readme on how this fake jwt is generated ? thanks !

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When I have time, I can create a separate PR for the readme of how this JWT is generated.

"config": {
"rules": [
{
"issuer": "https://cloud.google.com/iap",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Change to "https://example.token_service.com".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

return true;
}

bool AuthnUtils::ExtractOriginalPayload(const std::string& token,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

How do you decide when to extract original claims instead of normal claims? From the code, it seems that as long as "original_payload" claim exists, you think it is an AP token and extract from original payload? I don't think this is the right way to handle it. Admin should either specifically mention that this is AP token, or "iss" matches configured AP token issuer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If a token is an APToken, the original claims are extracted. Otherwise, normal claims are extracted. What token is considered an APToken is based on Quanjie's suggestion: if a token has an original_payload claim, and there is an issuer claim in the original_payload claim, and the issuer in the original_payload claim is different from the issuer of the token, the token is considered an APToken.
The rationale of such code is that a token exchange makes the issuers of the APToken and the original JWT to be different (if the two issuers are the same, there is no need for the token exchange).The code to check that the issuers are different are on the lines 125~129.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will the extracted original_payload be verified? If not, it seems there will be a security hole that the current request could impersonate any issuer and claims by using this original_payload field even if APToken is not enabled.

It might be better to only extract the original_payload if the outter jwt is issued by google.com. This prevents user/attacker to abuse the original_payload field without the need to do another verification.

/cc @quanjielin @diemtvu

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The extracted original_payload will not be verified by a recipient of the APToken: the APToken issuer verified the original JWT and the recipient of the APToken verified the APToken. If an APToken is valid, the recipient regards the payload is valid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@yangminzhu already gave an example why just checking "original_payload" (and issuers are different) is not enough to prove the token is indeed an AP token. My preferred way is to introduce APTokenConfig where users can specify their specific AP token properties (e.g., iss). If you want to work something out faster, an alternative is to standardize AP token header (e.g., x-ingress-authorization (TBD)). We can say that the token in the standardized header must be AP token.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

While agree with not doing this implicitly, I don't totally agree with Yangmin example. The original_payload (and all other claims in the outer jwt) are still verified by the outer jwt issuer, right

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Based on the discussion today, the code has been revised.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@diemtvu
Sorry for missing the background, I was trying to say what should we do when the outter issuer is compromised. In this case, the request could explicitly include the original_claims field, like the following one:

{
  "aud": "abc",
  "exp": 4698361508,
  "iat": 1544761508,
  "iss": "xyz.com",
  "original_claims": {
    "iss": "google.com",
    "sub": "userABC"
  }
}

The issuer xyz.com is compromised so the above JWT will pass the verification. However it contains the original_claims with google.com as the issuer and userABC as subject. This is not a problem for now because even if the xyz.com gets compromised, it still cannot impersonate the google.com.

@liminw
Could the attacker set the x-ingress-authorization header explicitly in their request to trick us to believe it's a JWT (APToken) returned by the token exchange service, right?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@yangminzhu "xyz.com" is listed by the operator as a trusted issuer, and we also do signature verification to make sure the token is signed by "xyz.com". So whatever contained in the token, we can assume that it is trusted.

"ingress-authorization" header is the default AP token location. The token put in there we can assume it is AP token. We will validate the token's signature to ensure that it is trusted. So I don't have concern on its security aspect.

auto original_payload_obj = json_obj->getObject(kAPTokenOriginalPayload);
std::string iss1 = json_obj->getString(kJwtIssuerKey, "");
std::string iss2 = original_payload_obj->getString(kJwtIssuerKey, "");
// Token exchange makes the issuers of the APToken and the original JWT
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This logic sounds like a hack. It basically says "if original_payload exists, and original issuer is not the same as new issuer, it is AP token". This is not how AP token is defined.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Based on the discussion today, the code has been revised.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we still need to compare issuer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I recommend comparing issuers because if an token exchange occurs, the issuer of the exchanged token should be different from the issuer of the original token. @quanjielin @liminw, what is your thought on comparing issuers?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't think it is a hard requirement that the issuers are different. We can probably drop the check here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done: the compare/check issuers have been removed from the code.

return AuthnUtils::ProcessJwtPayload(jwt_payload, payload->mutable_jwt());
std::string payload_to_process = jwt_payload;
std::string original_payload;
if (AuthnUtils::ExtractOriginalPayload(jwt_payload, &original_payload)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is the goal making AP token "transparent" to the service admin? I don't think this is right way of handling ap token. The service/namespace admin should be well aware that he/she is handling AP token, which is different from an external token.

return AuthnUtils::ProcessJwtPayload(jwt_payload, payload->mutable_jwt());
std::string payload_to_process = jwt_payload;
std::string original_payload;
if (AuthnUtils::ExtractOriginalPayload(jwt_payload, &original_payload)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agree with Limin. This should be configured explicitly. There could be use cases that admin want to keep the aptoken credential, or it can pack original payload differently (which we may need more than just a boolean flag).

I would suggest adding a flag to JWT configuration, and calling this function base on that value.

Regarding sending the original payload to backend, it's up to our decision on the feature. If we want user can use IAP without any change on there application, then it would be a nice option to have, though I think it would be more complicated. We can ignore that for now.

// The JWT issuer key name
static const std::string kJwtIssuerKey = "iss";
// The key name for the APToken original claims
static const std::string kAPTokenOriginalPayload = "original_payload";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we have this agreement on APToken format?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Based on the discussion today, original_payload has been changed to original_claims.

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What are these key/cert file for. Please add a README if they are used to generate test data, and/or how to replace this file if need to.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These key/cert files have been removed. I have added a guide of sending an example exchanged token to the jwt-authn filter and the Istio authn filter, and observing that the example backend echoes back the request when the authentication succeeds.

@@ -0,0 +1,116 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this file used in unit test? I don't see any code using this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This file is used in a guide I added. I have added a guide of sending an example exchanged token to the jwt-authn filter and the Istio authn filter, and observing that the example backend echoes back the request when the authentication succeeds.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's better to write a test. You can add them to test/integration. There is already few test setup examples.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

A test has been added.

return true;
}

bool AuthnUtils::ExtractOriginalPayload(const std::string& token,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

While agree with not doing this implicitly, I don't totally agree with Yangmin example. The original_payload (and all other claims in the outer jwt) are still verified by the outer jwt issuer, right

Copy link
Copy Markdown
Contributor

@quanjielin quanjielin left a comment

Choose a reason for hiding this comment

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

/lgtm

@@ -0,0 +1,116 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's better to write a test. You can add them to test/integration. There is already few test setup examples.

std::string payload_to_process = jwt_payload;
std::string original_payload;
bool found = false;
for (auto h : jwt.jwt_headers()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For readability, make a helper function (say IsAPTokenIssuer) for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
}
if (found &&
AuthnUtils::ExtractOriginalPayload(jwt_payload, &original_payload)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If it is APToken (found), but cannot extract original payload, then probably should return false.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Limin mentioned that an APToken may not necessarily contain the original payload (i.e., original_claims). If an APToken must contain the original payload, we can use it as a way to tell whether a token is an APToken. Hence, no false is returned here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is that so? Again, I feel wrong to while making assumption of the location, but not on the token format and it's behavior. Anyway, please add comment to explain the rationale.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done: added comments to explain the rationale.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think for the first version, it is ok to assume that AP token always carries original_claims. This may not be true going forward, but let's focus on this JWT token exchange use case for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Code has been revised to require an APToken to always carry original_claims: if it is APToken, but cannot extract original payload from original_claims, the code will return false.

@@ -68,7 +73,21 @@ bool AuthenticatorBase::validateX509(const iaapi::MutualTls& mtls,
bool AuthenticatorBase::validateJwt(const iaapi::Jwt& jwt, Payload* payload) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You should have new unit tests for this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Test added.

@diemtvu diemtvu removed the lgtm label Dec 14, 2018

namespace {
// The default header name for an exchanged token
static const std::string kExchangedTokenHeaderName = "x-ingress-authorization";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

use "ingress-authorization".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
}
if (found &&
AuthnUtils::ExtractOriginalPayload(jwt_payload, &original_payload)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Instead of doing string copy, you can directly put "payload_to_process" in the function.

AuthnUtils::ExtractOriginalPayload(jwt_payload, &payload_to_process)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If put "payload_to_process" in ExtractOriginalPayload(), ExtractOriginalPayload() may change "payload_to_process" even when ExtractOriginalPayload() returns false. Therefore, "payload_to_process" should not be put in ExtractOriginalPayload().

return true;
}

bool AuthnUtils::ExtractOriginalPayload(const std::string& token,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@yangminzhu "xyz.com" is listed by the operator as a trusted issuer, and we also do signature verification to make sure the token is signed by "xyz.com". So whatever contained in the token, we can assume that it is trusted.

"ingress-authorization" header is the default AP token location. The token put in there we can assume it is AP token. We will validate the token's signature to ensure that it is trusted. So I don't have concern on its security aspect.

@lei-tang lei-tang force-pushed the merge-authn-exchanged-token branch from a7ea99a to c1d0aab Compare December 15, 2018 08:39
// Return whether the header for an exchanged token is found
bool FindHeaderOfExchangedToken(const iaapi::Jwt& jwt) {
bool found = false;
for (auto h : jwt.jwt_headers()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we make assumption on the location value, should we also make assumption/requirement that the Jwt spec should declare only one header location. It seems weird (and could be wrong) to define more than one for aptoken.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

yes, sounds reasonable. We can require that there is only one header specified.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please change this per agreement. You can add check for jwt_headers() size and compare the only one in there.

}
}
if (found &&
AuthnUtils::ExtractOriginalPayload(jwt_payload, &original_payload)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is that so? Again, I feel wrong to while making assumption of the location, but not on the token format and it's behavior. Anyway, please add comment to explain the rationale.

EXPECT_TRUE(MessageDifferencer::Equals(expected_payload, *payload_));
}

TEST_F(ValidateJwtTest, OriginalPayloadOfExchangedToken) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add a test for the case when original payload was not there. Any may be another test with original payload, but jwt was not in the intended location.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done: added two more tests.

auto original_payload_obj = json_obj->getObject(kAPTokenOriginalPayload);
std::string iss1 = json_obj->getString(kJwtIssuerKey, "");
std::string iss2 = original_payload_obj->getString(kJwtIssuerKey, "");
// Token exchange makes the issuers of the APToken and the original JWT
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we still need to compare issuer?

static bool ProcessJwtPayload(const std::string& jwt_payload_str,
istio::authn::JwtPayload* payload);

// Parse the original_payload in an APToken.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit Parse -> Parses, Return -> Returns

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -0,0 +1,118 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add integration test to cover this setup, rather than giving sample.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

aptoken-envoy.conf and the user guide added in this PR are for a developer that is interested in going through the APToken authentication flow on Istio proxy. The code change in this PR is on validateJwt(). Three new tests on validateJwt() have been added to test the code change. An integration test will duplicate what these three new tests have tested with respect to the code change on validateJwt().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The tests you added already simulate the payload input to authN filter. It doesn't cover the full interaction between jwt filter and authn filter (and even mixer) as you can have with 'integration' test (I admit, the name is a bit misleading; it's still unit test, but testing few filters chained together instead of one).

Having sample config add a little benefit to the maintenance in the long run (config might change, overhead of setting up runtime etc). Given the way to set up the test envoy exactly likes the one specified by this config is fairly simple, I would suggest convert this into a test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The Istio authn integration tests that cover jwt filter, authn filter and mixer are in a different repository istio/istio under the path https://github.com/istio/istio/mixer/test/client. To add an integration test for authenticating an exchange token, this PR should be merged first, then the SHA in the istio.deps of the istio/istio proxy should be updated to the new SHA of istio-proxy, and finally an integration test for an exchanged token can be added under https://github.com/istio/istio/mixer/test/client. I have created an issue istio/istio#10514 to add an integration test in the istio/istio repository and assigned it to myself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done: integration tests have been added to https://github.com/istio/proxy/tree/master/test/integration.

@@ -0,0 +1,118 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The tests you added already simulate the payload input to authN filter. It doesn't cover the full interaction between jwt filter and authn filter (and even mixer) as you can have with 'integration' test (I admit, the name is a bit misleading; it's still unit test, but testing few filters chained together instead of one).

Having sample config add a little benefit to the maintenance in the long run (config might change, overhead of setting up runtime etc). Given the way to set up the test envoy exactly likes the one specified by this config is fairly simple, I would suggest convert this into a test.

@@ -0,0 +1,18 @@
This is a guide of sending an example exchanged token to
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

To echo the previous point about adding test, why developer needs to follow this series of steps to maintain/debug the code if they can do so with a simple "go test" command? Not to mention, the instruction is not clear what significant one needs to look for to see if APToken was handled correctly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The "go test" for running the Istio authn integration tests are in a different repository istio/istio under the path https://github.com/istio/istio/mixer/test/client. To add an integration test for authenticating an exchange token, this PR should be merged first, then the SHA in the istio.deps of the istio/istio proxy should be updated to the new SHA of istio-proxy, and finally an integration test for an exchanged token can be added. I have created an issue istio/istio#10514 to add an integration test in the istio/istio repository and assigned it to myself.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I mean https://github.com/istio/proxy/tree/master/test/integration. The one in istio repo is a plus, but not really necessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done: integration tests have been added to https://github.com/istio/proxy/tree/master/test/integration.

@lei-tang lei-tang force-pushed the merge-authn-exchanged-token branch from 805bc28 to 784474a Compare December 18, 2018 14:49
Payload expected_payload;
JsonStringToMessage(
R"({
"jwt": {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why original_claims is not shown up in the output?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because original_claims is not a string or string list.

// When the header of an exchanged token is found but the token
// does not contain the claim of the original payload, it
// is regarded as an invalid exchanged token.
ENVOY_LOG(error, "The token is invalid. The payload is {}",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please make the error message self-explained. Change to smt like: "Expect exchanged-token with original payload claim. Received: ", ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

// The default header name for an exchanged token
static const std::string kExchangedTokenHeaderName = "ingress-authorization";

// Return whether the header for an exchanged token is found
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Return -> Returns

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

static const std::string kExchangedTokenHeaderName = "ingress-authorization";

// Return whether the header for an exchanged token is found
bool FindHeaderOfExchangedToken(const iaapi::Jwt& jwt) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IsExchangedToken or IsAPToken may be a better name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

FindHeaderOfExchangedToken() accurately describes what this function does.

// Return whether the header for an exchanged token is found
bool FindHeaderOfExchangedToken(const iaapi::Jwt& jwt) {
bool found = false;
for (auto h : jwt.jwt_headers()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please change this per agreement. You can add check for jwt_headers() size and compare the only one in there.

const std::string kHeaderForExchangedToken = "ingress-authorization";

// Generates basic test request header.
Http::TestHeaderMapImpl BaseRequestHeaders() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm ok with this for now, but it would be nicer if you refactor these to a share library. Alternative (even better), you can have new test cases in the same test suit. They are not much different than the others.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done: added a check for jwt_headers() size and compare the only one in there.

@liminw
Copy link
Copy Markdown

liminw commented Dec 18, 2018

@lei-tang Based on conversation with Kurt today, AP token has the following format
Ingress-Authorization: Istio

I think the current JWT filter expects "Bearer ". For AP token, it should be "Istio " instead. This will require some changes in JWT filter, I think.

@liminw
Copy link
Copy Markdown

liminw commented Dec 18, 2018

cc @kmbarry1

@lei-tang
Copy link
Copy Markdown
Contributor Author

@liminw due to time constraints, I suggest staying with the agreed solution of using ingress-authorization header for alpha. The new "Ingress-Authorization: Istio" can be put in for the next release after its design is finalized. @quanjielin, @diemtvu, @wenchenglu

@liminw
Copy link
Copy Markdown

liminw commented Dec 19, 2018

@lei-tang It's not a "new" header. We are still using "Ingress-Authorization" header. It's just instead of "Ingress-Authorization: Bearer ", the format is "Ingress-Authorization: Istio ". I do not expect this will require a big code change. Can you evaluate it? We must sync with Kurt on the expected header. Otherwise, it won't work end-to-end. cc @kmbarry1

Copy link
Copy Markdown

@liminw liminw left a comment

Choose a reason for hiding this comment

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

/lgtm

@istio-testing
Copy link
Copy Markdown
Collaborator

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: lei-tang, liminw, quanjielin
To fully approve this pull request, please assign additional approvers.
We suggest the following additional approver: rshriram

If they are not already assigned, you can assign the PR to them by writing /assign @rshriram in a comment when ready.

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@JimmyCYJ
Copy link
Copy Markdown
Member

@lei-tang could you create another local branch with
git checkout -b merge-authn-exchanged-token upstream/release-1.1
Add your code and create another PR to release-1.1 branch?

I notice that you create this PR at 12/12, the release-1.1 branch might have diverged from master branch at that time. It would be risky to force current branch merge-authn-exchanged-token rebase to upstream/release-1.1. When updating Proxy SHA into istio/istio release-1.1 branch, this change would break pilot e2e tests.

static bool ProcessJwtPayload(const std::string& jwt_payload_str,
istio::authn::JwtPayload* payload);

// Parses the original_payload in an APToken.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is APToken a standard term or an new name defined in a design doc? Could you elaborate a little more?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed to exchanged token.


// Parses the original_payload in an APToken.
// Returns true if original_payload can be
// parsed successfully. Otherwise, return false.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: returns false.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@lei-tang
Copy link
Copy Markdown
Contributor Author

As the master branch is locked, #2070 is created to merge the code in this PR to the release-1.1 branch.

Copy link
Copy Markdown
Contributor

@diemtvu diemtvu left a comment

Choose a reason for hiding this comment

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

It's better have other PR submitted, then cherry pick to master (or vice versa).

@lei-tang
Copy link
Copy Markdown
Contributor Author

The PR #2070 has been merged to the release-1.1 branch. The PR #2071 is created to cherry-pick the PR #2070 commit from the release-1.1 branch to the master branch.

@istio-testing
Copy link
Copy Markdown
Collaborator

@lei-tang: PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@istio-testing istio-testing added the needs-rebase Indicates a PR needs to be rebased before being merged label Jan 10, 2019
@lei-tang
Copy link
Copy Markdown
Contributor Author

#2078 has cherry-picked this PR from release 1.1 branch to the master branch.

@lei-tang lei-tang closed this Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes Set by the Google CLA bot to indicate the author of a PR has signed the Google CLA. needs-rebase Indicates a PR needs to be rebased before being merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Authenticate an exchanged token

8 participants