How can I manipulate the username attribute included in a Vault Identity Token? - oauth

I am trying to set up the following in Hashicorp Vault:
GitHub Actions authenticates to Vault using JWT auth method. Vault creates an Identity Token containing the repo name. Actions can then use this token to authenticate to a Snowflake database, where I've set up Vault as an External OAuth server. The repo name will be used as the username in snowflake.
Here's the role with my token template:
resource "vault_identity_oidc_role" "github_actions" {
namespace = vault_namespace.namespace.path
name = "github_actions"
client_id = "https://mine.eu-north-1.aws.snowflakecomputing.com"
key = vault_identity_oidc_key.key.name
template = <<EOF
{
"scp": "session:role:${var.snowflake_role}",
"username": {{identity.entity.aliases.${vault_jwt_auth_backend.github_actions.accessor}.name}}
}
EOF
}
Everything seems like it should work fine. I get a token, it's valid, and Snowflake accepts it. But it tells me the username is "wrong". Testing manually, I found that usernames containing special characters just aren't accepted by snowflake. And using the repository field from GitHub gives me a username like "repo-owner/repo-name" which contains slashes and dashes and whatnot.
I'm thinking that if I can just manipulate this value in the token (replace slashes with "SLASH" or something), I'll end up with a username that Snowflake will accept. Is this possible, and if so, how?

You can tackle this on the JWT auth method.
The GitHub OIDC documentation shows a sample token that includes the field repository_id, which is simply a number with no problematic characters, so my first thought would be to switch to leveraging that field when creating your aliases.
If that repository_id field is NOT globally unique, I would create a different JWT Auth Method per GitHub organization that you're supporting. That way, you don't need to have the organization referenced in the alias name to create a unique alias, and the GitHub organization is defined by the location of the auth method mount. (vault auth enable -path="github.com/org-name" jwt might be a reasonable path in this pattern.)
If that repository_id field IS globally unique, then you only need to switch to that field in the JWT role definition user_claim parameter and you're done.
Yes, this will make things less user-friendly and more arcane - in order to resolve the repository ID, you'll have to do a GitHub API call, which will make audit log review include additional steps - but I don't see any other point at which you have control over the strings in the auth flow.

Related

Getting a Jira Issue via OAuth Authentication using Postman

I am trying to GET an issue in Jira using POSTMAN. I have selected Type as OAuth 1.0. For that it is asking me some fields mentioned below. I have generated Token and Token Secret which I am passing to it. I have also configured my generic application to Jira in Application links. I am not aware of what to be passed in Consumer Secret and Signature Method (what should be the signature method). I am currently selecting HMAC-SHA1. For rest of the fields it is generating values based on the parameters passed above.
Consumer Key : hardcoded-consumer
Consumer Secret : ?? (What should I pass here)
Token : ojn33TZALMlvp5eCa6HeErDSx9K8LL6A
Token Secret : inHfn2QFJkkYkWQ8FxT9mXkdcoNxYPf5
Signature Method : HMAC-SHA1
Timestamp : 1474290363 (Generated value)
Nonce : x1hs2v (Generated value)
Version : 1.0 (Generated value)
Realm : (It is optional)
After hitting my jira Url it is giving me oauth_problem=token_rejected error. Can anyone tell me where I am making the mistake?
Here is the Jira URL which I am hitting :
http://bmh1060149:8080/rest/api/2/issue/NWFM-1 (NWFM-1 is the Jira issue)
Please find the below screen shot for more reference.
After little bit of research I found the answer. Once we get the access token we can directly pass that to your Jira Url. There is no need to pass all those parameters.
To get all issue types we can use the following URL and passing access token as an argument.
http://bmh1060149:8080/rest/api/2/issuetype?access_token=euyyIxB6q5waBHeZ9zB7kGV21GRNNOud
Please see the attached screen shot for more reference.

OAuth2 token returned from Office 365 doesn't contain a preffered_username claim

I followed this tutorial and have gotten to the point where I am decoding the returned token, and extracting the email address (which should be stored in the preferred_username property), ie, the following code:
decoded_token = Base64.urlsafe_decode64(encoded_token)
jwt = JSON.parse(decoded_token)
email = jwt['preferred_username']
The problem is that the object returned doesn't contain this property, what I do get back is similar to below:
{
"ver":"2.0",
"iss":"https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0",
"aud":"0ab6433e-84fc-469b-8c72-41f7a0241a61",
"exp":1458142389,
"iat":1458055989,
"at_hash":"0OYaLKpTTdHNBrQNOqwQ0Q",
"sub":"AAAAAAAAAAAAAAAAAAAAAC1TrOaOmvInYrFAyrQjlFI",
"tid":"9188040d-6c67-4c5b-b112-36a304b66dad"
}
A quick glance at the spec indicates I am getting the correct object back from Office 365, as preferred_username is mentioned as a potential claim, but it isn't in the object I get back.
It's possible I'm not calling the get_token function with the correct parameters, but the documentation for the library is pretty sparse, so I can't really tell.
I have raised an issue on Github.
Is this an error on the Office 365 end, an error with the tutorial, or am I doing something wrong myself?
Answered here by Jason Johnston from Microsoft (author of the tutorial):
The Azure team deployed a breaking change to their v2 auth endpoint, which is causing the preferred_username to not be present. You need to add profile to the SCOPES array in auth_helper.rb. I'll post an update to the tutorial after the Build conference.
The SCOPES array in auth_helper.rb now looks like so:
SCOPES = [ 'openid', 'https://outlook.office.com/mail.read', 'profile' ]
I am try to reproduce this issue using normal HTTP request however I could get the preferred_username property successfully.
As far as I know, we can get this property only when we specific the openid scope in the request. To narrow down this issue, I suggest that you trying use Fiddler or Postman without Ruby.
Here is the test using web browser and Fiddler to get the id token for your reference:
Register the app in the portal using Office 365 account( which you can refer to the tutorial)
Get the auth code in a web broswer via the link below:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={ClientID}&response_type=code&redirect_uri={RedirectURL}&response_mode=query&scope=https%3A%2F%2Foutlook.office.com%2Fmail.read%20https%3A%2F%2Foutlook.office.com%2Fmail.send%20openid&state=12345
Replace the auth code from preview request and using Fiddler to post the request to get the tokens:
POST: https://login.microsoftonline.com/common/oauth2/v2.0/token
grant_type=authorization_code&client_id={ClientID}&scope=https%3A%2F%2Foutlook.office.com%2Fmail.read%20https%3A%2F%2Foutlook.office.com%2Fmail.send%20openid&redirect_uri=http%3A%2F%2Flocalhost%3A55065%2F&client_secret={ClientSecret}&code={AuthCode}
Decode the ID token from the link below:
https://jwt.io/
Then I could get the preferred_username property from the ID token successfully.

Is it possible to get profile information in an id_token from Google?

When using Google's OpenIDConnect authentication system, it's possible to specify email or profile or both in the scope parameter. If you request the email scope, the "email" and "email_verified" claims will be included in the id_token that gets returned as part of a successful OAuth2 authentication session.
Here's an example from Google's documentation:
An ID token's payload
An ID token is a JSON object containing a set of name/value pairs.
Here’s an example, formatted for readability:
{"iss":"accounts.google.com",
"at_hash":"HK6E_P6Dh8Y93mRNtsDB1Q",
"email_verified":"true",
"sub":"10769150350006150715113082367",
"azp":"1234987819200.apps.googleusercontent.com",
"email":"jsmith#example.com",
"aud":"1234987819200.apps.googleusercontent.com",
"iat":1353601026,
"exp":1353604926,
"hd":"example.com"
}
However, requesting the profile scope seems to have no effect whatsoever on the contents of the id_token. In order to retrieve the profile information, you have to make a separate HTTP request to a distinct endpoint (authenticated with the access_token you just received) to get a document that looks very similar, but with more information:
{
"kind": "plus#personOpenIdConnect",
"gender": string,
"sub": string,
"name": string,
"given_name": string,
"family_name": string,
"profile": string,
"picture": string,
"email": string,
"email_verified": "true",
"locale": string,
"hd": string
}
Ideally, I would prefer to get the profile information (just name, actually) included in the id_token JWT rather than having to make a separate call. Is there any way to specify additional fields and have them included as claims in the id_token? If not, why is email treated specially and returned in the id_token?
Starting today you will get profile information when exchanging the code at the token endpoint (i.e. using the "code flow").
How to use: add the profile scope to your request, and make sure you are using the OpenID Connect compliant endpoints (the ones listed in https://accounts.google.com/.well-known/openid-configuration).
Look for claims such as name and picture in these ID Token responses. As before, if the email scope is in your request, the ID Token will contain email related claims.
When you refresh your access token, every so often the ID Token that is returned with the fresh access token will also contain these additional claims. You can check these fields, and if present (and different to what you have stored), update your user's profile. This can be useful to detect name or email address changes.
When a request is made with response_type=id_token and profile in the scope like scope=openid+profile+email, the resulting id token should contain the profile claims directly in it.
This is per section 5.4 of the OpenID Connect spec, which says "... when no Access Token is issued (which is the case for the response_type value id_token), the resulting Claims are returned in the ID Token."
However, in a little testing I did with their OAuth 2 Playground, Google doesn't seem to put profile claims in the id token even when response_type=id_token and no access token is issued. I'd argue that this is an implementation defect on Google's part and, short of them fixing that (or adding support for the "claims" Request Parameter), there doesn't seem to be a way to accomplish what you're looking for.
Well, this is the right place to request. We are working to support this feature and should be rolling this out soon (in the next few weeks). I'll make an update to this response then.

Apigee doesn't seem to support the OAuth 2 specification, is there a reason why?

We're making requests for bearer tokens using client_credentials OAuth 2 grant flow with Apigee. According to the spec:
4.4.2. Access Token Request
The client makes a request to the token endpoint by adding the
following parameters using the "application/x-www-form-urlencoded"
format per Appendix B with a character encoding of UTF-8 in the HTTP
request entity-body:
grant_type
REQUIRED. Value MUST be set to "client_credentials".
If we make a call however we get an error like this:
{"ErrorCode" : "invalid_request", "Error" :"Required param : grant_type"}
It seems that using Apigee we have to send grant_type as a query parameter.
Why is this? We have clients of Apigee that are unable to use OAuth libraries in their language of choice because of the way that Apigee deals with OAuth 2, and it would be good to know if there is by-design or not.
In addition it doesn't seem like it supports grant_type in the post body and sending id and key using basic auth.
Turns out you do not need to send in grant_type as a query parameter. There is a <GrantType> element in your GenerateAccessToken policy that takes in a variable. For instance, I can use the following:
<OAuthV2 name="GenerateAccessToken">
<DisplayName>GenerateAccessToken</DisplayName>
<FaultRules/>
<Properties/>
<!-- This policy generates an OAuth 2.0 access token using the password grant type -->
<Operation>GenerateAccessToken</Operation>
<!-- This is in millseconds -->
<ExpiresIn>1800000</ExpiresIn>
<Attributes/>
<SupportedGrantTypes>
<GrantType>password</GrantType>
</SupportedGrantTypes>
<GenerateResponse enabled="false">
<Format>FORM_PARAM</Format>
</GenerateResponse>
<GrantType>user.grant_type</GrantType>
<UserName>request.header.username</UserName>
<PassWord>request.header.password</PassWord>
</OAuthV2>
In this example, the grant_type is passed in as user.grant_type. But user.grant_type can be anything-- header, query param, form param, or even a hard-coded value. This way, you (the developer) are provided maximum flexibility on how you want to send in the grant_type.
Can you paste the exact API call that you are making (obviously you should obfuscate the key and secret)?
I'd like to understand what you say when you say "Apigee" -- it could mean API BAAS (https://api.usergrid.com) or a proxy that you defined using API services and attached an OAuth 2 policy to, or something else?

Oauth 2 - params ordering and signature integrity

I have two questions:
Q1: Why does OAuth2 require params to be ordered and encoded (for 2-legged)?
All it has to worry about is the matching signature in both the end for the given data(query string).
We can just check the signature generated using the query string.(e.g ?a=1&b=2). Since the signature is generated based on the secret key which is known only to the client and provider, we can only consider the query string without any ordering/encoding.
So, what's the advantage in doing ordering/encoding and then creating the signature?
Q2: How can this signature save me from man-in-the middle attack?
If I have to make a request like this to my server from a client:
increaseUserPoints?userId=1&pointsToAdd=5&appId=x&token=XYZ
Now the token XYZ will be always same, so a hacker could keep posting the same request to increase points. Since the generated token from the given appId is the same, the server will allow this. How is this case handled?
Q1: Ordering the query parameters brings sanity to the HMAC.
Let's say you have two parameters: 'pointsToAdd' and 'appId'. Using the query string pointsToAdd=X&appID=y creates a different HMAC to appID=y&pointsToAdd=X. Because both you and the server need to generate the same HMAC to verify the requests having unordered query parmeters plain fails.
Q2: This saves you from an attack because only you and the server know how to sign your request.
You have a secret key, and only you and the server knows it. This key signs the request. If the HMAC doesn't match according to this secret key, the request fails.
Because all parameters have been used to create the HMAC the request is secure from MITM attacks — a hacker can't change, add or delete any query parameters, or the server will produce a different HMAC when it attempts to authorise and the request fails.

Resources