How to use Workload identity to access ESP in the Google Kubernetes Engine with the Google Cloud .NET SDK? - kubernetes

Background
On the Google Kubernetes Engine we've been using Cloud Endpoints, and the Extensible Service Proxy (v2) for service-to-service authentication.
The services authenticate themselves by including the bearer JWT token in the Authorization header of the HTTP requests.
The identity of the services has been maintained with GCP Service Accounts, and during deployment, the Json Service Account key is mounted to the container at a predefined location, and that location is set as the value of the GOOGLE_APPLICATION_CREDENTIALS env var.
The services are implemented in C# with ASP.NET Core, and to generate the actual JWT token, we use the Google Cloud SDK (https://github.com/googleapis/google-cloud-dotnet, and https://github.com/googleapis/google-api-dotnet-client), where we call the following method:
var credentials = GoogleCredential.GetApplicationDefault();
If the GOOGLE_APPLICATION_CREDENTIALS is correctly set to the path of the Service Account key, then this returns a ServiceAccountCredential object, on which we can call the GetAccessTokenForRequestAsync() method, which returns the actual JWT token.
var jwtToken = await credentials.GetAccessTokenForRequestAsync("https://other-service.example.com/");
var authHeader = $"Bearer {jwtToken}";
This process has been working correctly without any issues.
The situation is that we are in the process of migrating from using the manually maintained Service Account keys to using Workload Identity instead, and I cannot figure out how to correctly use the Google Cloud SDK to generate the necessary JWT tokens in this case.
The problem
When we enable Workload Identity in the container, and don't mount the Service Account key file, nor set the GOOGLE_APPLICATION_CREDENTIALS env var, then the GoogleCredential.GetApplicationDefault() call returns a ComputeCredential instead of a ServiceAccountCredential.
And if we call the GetAccessTokenForRequestAsync() method, that returns a token which is not in the JWT format.
I checked the implementation, and the token seems to be retrieved from the Metadata server, of which the expected response format seems to be the standard OAuth 2.0 model (represented in this model class):
{
"access_token": "foo",
"id_token": "bar",
"token_type": "Bearer",
...
}
And the GetAccessTokenForRequestAsync() method returns the value of access_token. But as far as I understand, that's not a JWT token, and indeed when I tried using it to authenticate against ESP, it responded with
{
"code": 16,
"message": "JWT validation failed: Bad JWT format: Invalid JSON in header",
..
}
As far as I understand, normally the id_token contains the JWT token, which should be accessible via the IdToken property of the TokenResponse object, which is also accessible via the SDK, I tried accessing it like this:
var jwtToken = ((ComputeCredential)creds.UnderlyingCredential).Token.IdToken;
But this returns null, so apparently the metadata server does not return anything in the id_token field.
Question
What would be the correct way to get the JWT token with the .NET Google Cloud SDK for accessing ESP, when using Workload Identity in GKE?

To get an IdToken for the attached service account, you can use GoogleCredential.GetApplicationDefault().GetOidcTokenAsync(...).

Related

Keycloak REST API 401 when using custom client with service account

we want to access the Keycloak (Version 10) REST API from one of our backend services. To authenticate, we have setup the followings:
new clients within our realm
enabled service account for that client
assigned all [1] roles of “realm-management” to the services account
Accessing the API, e.g. fetching a selected user always results in a 401 response.
Steps to make the requests are:
Retrieve access_token from https://my-keycloak.com/auth/realms/my-realm/protocol/openid-connect/token using grant_type=client_credentials + Client ID + Client Secret
Fetch user from https://my-keycloak.com/auth/realms/my-realm/users/some-user-id-4711
using the Authorization: Bearer $ACCESS_TOKEN with the Token from step 1.
My Question: Is it even possible to use a custom client or do we have to stick to login via admin-cli? How would we need to configure the custom client, to grant access to the REST API.
Thanks,
Martin
[1] Simply setting all roles for the sake of testing, regardless that we only want to read data in the end.

sign request to AWS API gateway with Signature v4 from using AWS Cognito token

I created a AWS API Gateway set with authentication = AWS_IAM to call a Lambda function. Now, to call this API I understand that I need to sign the request and as stated in the AWS documentation the correct way is to add the Authorization header calculated using AWS Signature V4 which need an access_key and a secret_key.
On my client side the user authenticate with AWS Cognito first and receive the JWT tokens (id token access token and refresh token) but I cannot find the access_key/secret
_key in them. How can I calculate the AWS Signature V4 from the tokens received from AWS Cognito?
I believe you can't (with 99,99999 of certainty)!
Please confirm that you are authenticating your users with AWS Cognito User Pool. You probably are because Cognito User Pool is the service that provides JWT. In this case, the token will assure the service that receives it (API Gateway) that your user is registered in a specific identity directory (User Pool). Your service should evaluate if it will provide access or not to its resources for users registered in this specific directory with the provided claims (groups, roles, etc).
When you secure your API Gateway endpoints with AWS_IAM you are saying that only identities that AWS can recognize inside its own identity directory (Users or Roles) are allowed to perform actions on the resource. In general, users registered in Cognito User Pools are not considered by AWS as valid identities.
For a Cognito User Pool user to be considered a valid AWS identity, you have two options:
1 - Configure your AWS account to use external Identity Providers and Federation. Not a simple thing and a solution to a different use case. In summary, don't choose this one.
2 - Use another AWS product (with a name that creates a lot of confusion) called Cognito Identity Pool. This service evaluates if the JWT token is allowed in that context (you configure it inside the Identity Pool). If is a valid token from a registered identity directory, Cognito Identity Pool will exchange your JWT token for a AWS Access Key, AWS Secret Key and AWS Session Token associated with a specific IAM Role. You can then use these keys to sign your request. But keep in mind that with this change you will lose your capacity to identity the specific user in API Gateway and in the downstream services called by API Gateway.
If you need to have the JWT token in your downstream services, you can do it with a little bit of additional effort. You can't find a way here: https://stackoverflow.com/a/57961207/6471284

How to create base authentication in kubernetes?

I want to create base authentication in kubernetes. every document say that I should create CSV or file then enter the username and password in it. but I do not want to use file I want to some database or kubernetes handle it.
what can I do for base authentication?
You can based your authentication on tokens if you don't want to use static pasword file.
First option:
Service Account Tokens
A service account is an automatically enabled authenticator that uses signed bearer tokens to verify requests.
The plugin uses two flags(which are optional):
Service accounts are usually created automatically by the API server and associated with pods running in the cluster through the ServiceAccount Admission Controller. Bearer tokens are mounted into pods at well-known locations, and allow in-cluster processes to talk to the API server. Accounts may be explicitly associated with pods using the serviceAccountName field of a PodSpec.
Service account bearer tokens are perfectly valid to use outside the cluster and can be used to create identities for long standing jobs that wish to talk to the Kubernetes API. To manually create a service account, simply use the kubectl create serviceaccount (NAME) command. This creates a service account in the current namespace and an associated secret.
The created secret holds the public CA of the API server and a signed JSON Web Token (JWT).
The signed JWT can be used as a bearer token to authenticate as the given service account. See above for how the token is included in a request. Normally these secrets are mounted into pods for in-cluster access to the API server, but can be used from outside the cluster as well.
There is some drawbacks because service account tokens are stored in secrets, any user with read access to those secrets can authenticate as the service account. Be careful when granting permissions to service accounts and read capabilities for secrets.
Second:
Install OpenID Connect (full documentation you can find here: oidc).
OpenID Connect (OIDC) is a superset of OAuth2 supported by some service providers, notably Azure Active Directory, Salesforce, and Google. The protocol’s main addition on top of OAuth2 is a field returned with the access token called an ID Token. This token is a JSON Web Token (JWT) with well known fields, such as a user’s email, signed by the server.
To identify the user, the authenticator uses the id_token (not the access_token) from the OAuth2 token response as a bearer token.
Since all of the data needed to validate who you are is in the id_token, Kubernetes doesn’t need to “phone home” to the identity provider. In a model where every request is stateless this provides a very scalable solution for authentication.
Kubernetes has no “web interface” to trigger the authentication process. There is no browser or interface to collect credentials which is why you need to authenticate to your identity provider first.
There’s no easy way to authenticate to the Kubernetes dashboard without using the kubectl proxy command or a reverse proxy that injects the id_token.
More information you can find here: kubernetes-authentication.

Identity Server 4 issued JWT Validation failure

I have an Identity Server running based on IdentityServer 4 (.Net Core v2) targeting the full .Net framework, and I have an ASP.NET WebAPI built against ASP.Net Web API 2 (i.e. NOT .Net Core) that is using the Identity Server 3 OWIN middleware for token authentication.
When running locally, everything works just fine - I can use Postman to request an Access Token from the Identity Server using a RO Password flow, and I can then make a request to the WebAPI sending the token as a Bearer token - all works fine.
Now, when everything is hosted on our test servers, I get a problem when calling the WebAPI - I simply get an Unauthorized response. The token returned from the Identity server is ok (checked using http://jwt.io), but validation of the JWT is failing in the WebAPI.
On further investigation, after adding Katana logging, I see that a SecurityTokenInvalidAudienceException is being reported.
Audience validation failed. Audiences:
'https://11.22.33.44:1234/resources, XXXWebApi'. Did not match:
validationParameters.ValidAudience: 'https://localhost:1234/resources'
or validationParameters.ValidAudiences: 'null'
Looking at the JWT audience, we have:
aud: "https://11.22.33.44:1234/resources", "XXXWebApi"
In the WebAPI Startup, I have the call to
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = , // path to our local ID Server
ClientId = "XXXWebApi",
ClientSecret = "XXX_xxx-xxx-xxx-xxx",
RequiredScopes = new[] { "XXXWebApi" }
});
So the JWT audience looks ok, but its obviously not matching with what is supplied by the middleware (built from the IdP discovery end point). I would have thought that because I am specifying the RequiredScopes to include XXXWebApi that would have been enough to match the JWTs audience but that seems to be ignored.
I'm unsure what to change in the WebAPI authentication options to make this work.
EDIT: I changed the WebAPI Token auth options to use the validation endpoint, and this also fails in the IdentityServer with the same error.
If I call the Identity Server introspection endpoint directly from Postman with the same token though, it succeeds.
Ok, so after a lot of head scratching and trying various things out I at least have something working.
I had to ensure the Identity Server was hosted against a publicly available DNS, and configure the Authority value in the IdentityServerBearerTokenAuthenticationOptions to use the same value.
That way, any tokens issued have the xx.yy.zz full domain name in the JWT audience (aud), and when the OWIN validation middleware in the WebAPI verifies the JWT it uses the same address for comparison rather than localhost.
I'm still slightly confused why the middleware cant just use the scope value for validation because the token was issued with the API resource scope (XXXWebAPi) in the audience, and the API is requesting the same scope id/name in the options as shown.
As far as I understand your WebAPI project is used as an API resource.
If so - remove the 'clientId' and 'clientSecret' from the UseIdentityServerBearerTokenAuthentication, keep the 'RequiredScopes' and the authority (you may also need to set ValidationMode = ValidationMode.Both).
You need them, when you are using reference tokens. From what you've said - you are using a JWT one. Check here, here and here.

Validate oAuth 2 access token in APIGEE without VerifyOAuthTokens policy

We are using Apigee as our Authorization Server (AS) and we have a few Spring Restful services deployed in IBM Bluemix public cloud which acts as our Resource server (RS).
Each of the services has an equivalent proxy service configured in Apigee. For the proxy services, we have configured the VerifyOAuthTokens policy to verify the token passed by the user and return an error if invalid token is passed
The problem is, since our RS is in the public cloud (no plans or need of moving to a dedicated or private cloud) the api endpoints are open and can be invoked by anyone who knows the url.Though the expectation is everyone should call the apis via APIGEE proxies but we cannot force that since we are in public cloud and there are no options of opening ports coming from apigee or something. We would like to take the following approach to secure the api endpoints.
Accept the Authorization header for each call
Take the token and call a validate token service in Apigee
For 2, We are not able to find an APIGEE api which can validate an access token similar to say googles
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=1/fFBGRNJru1FQd44AzqT3Zg
or Github's
GET /applications/:client_id/tokens/:access_token
Is there actually an external APIGEE service to validate a token?
If not, what would be the best way to make sure that only valid users with valid tokens can access the apis?
Thanks,
Tatha
Did you look at this post in the Apigee Community: Using third-party OAuth tokens
We did something similar to this but not using oauth tokens. We used Apigee to do a callout to a third party IDP (identity provider). The 3rd party IDP wasn't able to generate tokens but exposed a web service to authenticate the user. If the user was authenticated successfully (based on interpreting the result received back from the target endpoint webservice), then you tell Apigee that it was successful by setting the external authorization status to true (step #2 in the link).
NOTE: this has to be done inside an Assign Message Policy step PRIOR to the GenerateAccess token operation. Apigee interprets this as a successful authorization and then can generate a valid oauth token that the caller can then send along to access the protected API.