A few questions about authentication and authorization with Kong jwt in microservices architecture - authentication

As a newbie in microservices architecture, I need to ask a few questions about implementing JWT authentication using Kong.
The architecture of my application looks like in the picture below:
So far I have only used Kong as a proxy and load balancer. The Authentication Service was responsible for creating the token. The token was created during registration and logging in. During registration or logging in, the authentication service asked the user service, and the user service checked the user's data in the mongodb database. Each endpoint from the other services had to receive a JWT in the header and had a function along with a secret which decoded the token. However, it seems to me that this is an unnecessary duplication of code and the whole process of creating and decoding JWT may or even should be done in Kong with JWT plugin.
I tried to follow a couple of tutorials and YouTube guides just like this one:
JWT Kong Gateway
Unfortunately each of the tutorials shows how to create a JWT only for a single consumer, without Kong being connected to the base.
My kong.yml file:
_format_version: "3.0"
_transform: true
services:
- name: building_service
url: http://building_service/building
routes:
- name: building_service_route
paths:
- /building
- name: user_service
url: http://user_service/user
routes:
- name: user_service_route
paths:
- /user
- name: role_service
url: http://role_service/role
routes:
- name: role_service_route
paths:
- /role
- name: task_service
url: http://task_service/task
routes:
- name: task_service_route
paths:
- /task
- name: authorization_service
url: http://authorization_service/authorization
routes:
- name: authorization_service_route
paths:
- /authorization
plugins:
- name: jwt
route: building_service_route
enabled: true
config:
key_claim_name: kid
claims_to_verify:
- exp
# consumers:
# - username: login_server_issuer
# jwt_secrets:
# - consumer: login_server_issuer
# secret: "secret-hash-brown-bear-market-rate-limit"
- name: bot-detection
- name: rate-limiting
config:
minute: 60
policy: local
Kongo service in docker-compose.yml:
services:
kong:
build: ./App/kong
volumes:
- ./App/kong/kong.yml:/usr/local/kong/declarative/kong.yml
container_name: kong
environment:
KONG_DATABASE: 'off'
KONG_PROXY_ACCESS_LOG: '/dev/stdout'
KONG_ADMIN_ACCESS_LOG: '/dev/stdout'
KONG_PROXY_ERROR_LOG: '/dev/stderr'
KONG_ADMIN_ERROR_LOG: '/dev/stderr'
KONG_ADMIN_LISTEN: "0.0.0.0:8001, 0.0.0.0:8444 ssl"
KONG_DECLARATIVE_CONFIG: "/usr/local/kong/declarative/kong.yml"
command: "kong start"
networks:
- api-network
ports:
- "8000:8000"
- "8443:8443"
- "127.0.0.1:8001:8001"
- "127.0.0.1:8444:8444"
List of my questions:
How authentication service should connect to Kong and create JWT with chosen user (as I understand consumer) data?
Should Kong be somehow connected to database to get required user data and create secret?
How to decode JWT with kong and transfer it to other services in header?
Can anyone provide an example of how to achieve desired result?
Do I misunderstood something about JWT or Kong and what I want to achieve is impossible?

If you can consider using Keycloak for user management, then you can have a look at the jwt-keycloak plugin:
https://github.com/gbbirkisson/kong-plugin-jwt-keycloak

Related

Secure mTLS communication within Istio-knative services + external requests

We are converting existing k8s services to use istio & knative. The services receive requests from external users as well as from within the cluster. We are trying to setup Istio AuthorizationPolicy to achieve the below requirements:
Certain paths (like docs/healthchecks) should not require any special header or anything and must be accessible from anywhere
Health & metric collection paths required to be accessed by knative must be accisible only by knative controllers
Any request coming from outside the cluster (through knative-serving/knative-ingress-gateway basically) must contain a key header matching a pre-shared key
Any request coming from any service within the cluster can access all the paths
Below is a sample of what I am trying. I am able to get the first 3 requirements working but not the last one...
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: my-svc
namespace: my-ns
spec:
selector:
matchLabels:
serving.knative.dev/service: my-svc
action: "ALLOW"
rules:
- to:
- operation:
methods:
- "GET"
paths:
- "/docs"
- "/openapi.json"
- "/redoc"
- "/rest/v1/healthz"
- to:
- operation:
methods:
- "GET"
paths:
- "/healthz*"
- "/metrics*"
when:
- key: "request.headers[User-Agent]"
values:
- "Knative-Activator-Probe"
- "Go-http-client/1.1"
- to:
- operation:
paths:
- "/rest/v1/myapp*"
when:
- key: "request.headers[my-key]"
values:
- "asjhfhjgdhjsfgjhdgsfjh"
- from:
- source:
namespaces:
- "*"
We have made no changes to the mTLS configuration provided by default by istio-knative setup, so assume that the mtls mode is currently PERMISSIVE.
Details of tech stack involved
AWS EKS - Version 1.21
Knative Serving - Version 1.1 (with Istio
1.11.5)
I'm not an Istio expert, but you might be able to express the last policy based on either the ingress gateway (have one which is listening only on a ClusterIP address), or based on the SourceIP being within the cluster. For the latter, I'd want to test that Istio is using the actual SourceIP and not substituting in the Forwarded header's IP address (a different reasonable configuration).

Express Gateway degraded causing huge delays on requests

I have a Gateway configured to handle some services, but there is a huge performance issue and I don't know how can I address this. This is the config for this particular service:
http:
port: 3020
hostname: 'xxxx.prod.xx.xxx.net'
apiEndpoints:
laPrdTest:
host: 'xxx-xxxxxxxx.xxxxxxxx.com'
paths: '/api/v1/esign/*'
serviceEndpoints:
laPrdTest:
urls:
- http://xxxxxx-app1.prd.xxx.xxxxxxxx.net
- http://xxxxxx-app2.prd.xxx.xxxxxxxx.net
policies:
- basic-auth
- cors
- expression
- log
- jwt
- proxy
- rate-limit
pipelines:
laPrdTest:
apiEndpoints:
- laPrdTest
policies:
- cors:
- log:
- action:
message: ${req.method} ${req.originalUrl} ${JSON.stringify(req.headers)}
- rate-limit:
- action:
max: 50
windowMs: 120000
rateLimitBy: "${req.ip}"
- proxy:
- action:
serviceEndpoint: laPrdTest
changeOrigin: true
Check on this gif so you can see how this is very bad on performance:
And this is a call directly to the service API with no performance issue:
Why is this happening?
What can I do to solve this issue?
Finally I couldn't use this anymore an move to do the proxy through Nginx directly.
Any request made through the Gateway it took seconds almost minutes and now using Nginx as a gateway it takes miliseconds.

502 Bad Gateway Error After Instituting AuthorizationPolicy from Istio Documentation

i'm using Istio 1.5.4 and trying apply the example referenced here:
https://istio.io/latest/docs/tasks/security/authentication/authn-policy/#end-user-authentication
Everything works as expected until defining the AuthorizationPolicy - the moment i introduce that i would get a 502 Bad Gateway error regardless if i provide a valid JWT token or not.
On a secondary note, I'm able to get the AuthorizationPolicy to work properly if i update the example to be applied at my own service namespaced level. Then RequestAuthentication + AuthorizationPolicy would work as expected, however, i would run into a different roadblock where now internal service would also require a valid jwt token.
authentication/authorization internal service issue
I've discovered that the 502 is a result of my loadbalancer health check failing due to the AuthorizationPolicy applied. Adding a conditional header User-Agent against my healh check probe seems to do the trick, but then i get back the net effect where no token provided is still getting through
No token is getting through because that´s how you configured your AuthorizationPolicy, that´s how source: requestPrincipals: ["*"] works. Take a look at this example.
RequestAuthentication defines what request authentication methods are supported by a workload. If will reject a request if the request contains invalid authentication information, based on the configured authentication rules. A request that does not contain any authentication credentials will be accepted but will not have any authenticated identity. To restrict access to authenticated requests only, this should be accompanied by an authorization rule. Examples:
Require JWT for all request for workloads that have label app:httpbin
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: httpbin
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
jwtRules:
- issuer: "issuer-foo"
jwksUri: https://example.com/.well-known/jwks.json
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
rules:
- from:
- source:
requestPrincipals: ["*"]
Use requestPrincipals: ["testing#secure.istio.io/testing#secure.istio.io"] instead as mentioned here, then it will accept only requests with token.
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: frontend
namespace: default
spec:
selector:
matchLabels:
app: frontend
jwtRules:
- issuer: "testing#secure.istio.io"
jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.5/security/tools/jwt/samples/jwks.json"
The second resource is an AuthorizationPolicy, which ensures that all requests have a JWT - and rejects requests that do not, returning a 403 error.
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: require-jwt
namespace: default
spec:
selector:
matchLabels:
app: frontend
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["testing#secure.istio.io/testing#secure.istio.io"]
Once we apply these resources, we can curl the Istio ingress gateway without a JWT, and see that the AuthorizationPolicy is rejecting our request because we did not supply a token:
$ curl ${INGRESS_IP}
RBAC: access denied
Finally, if we curl with a valid JWT, we can successfully reach the frontend via the IngressGateway:
$ curl --header "Authorization: Bearer ${VALID_JWT}" ${INGRESS_IP}
Hello World! /

How to implement redirect (301 code) mock in serverless framework config (for AWS) without lambda

I'd like that the root path of my API redirect (301) to completely another site with docs. So I have a lambda at e.g /function1 path and the / should return code 301 with another location. And I'd like to do it without another lambda.
This is exactly what is described here, but via aws command line tool. I tried this approach - it works perfectly, but I'd like to configure such API gateway mock via serverless framework config.
Fortunately, the series of CLI commands you linked to can be reproduced in CloudFormation, which can then be dropped into the Resources section of your Serverless template.
In this example, a GET to /function1 will invoke a lambda function, while a GET to / will return a 301 to a well-known search engine.
service: sls-301-mock
provider:
name: aws
runtime: nodejs12.x
stage: dev
region: us-east-1
functions:
hello:
handler: handler.hello
events:
- http:
path: function1
method: get
resources:
Resources:
Method:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId:
!GetAtt ApiGatewayRestApi.RootResourceId
RestApiId:
Ref: ApiGatewayRestApi
AuthorizationType: NONE
MethodResponses:
- ResponseModels: {"application/json":"Empty"}
StatusCode: 301
ResponseParameters:
"method.response.header.Location": true
Integration:
Type: MOCK
RequestTemplates:
"application/json": "{\n \"statusCode\": 301\n}"
IntegrationResponses:
- StatusCode: 301
ResponseParameters:
"method.response.header.Location": "'https://google.com'"
Tested with:
Framework Core: 1.62.0
Plugin: 3.3.0
SDK: 2.3.0
Components Core: 1.1.2
Components CLI: 1.4.0
Notes
ApiGatewayRestApi is, by convention, the logical name of the API Gateway Stage resource created by Serverless on account of the http event.
Relevant CloudFormation documentation
ApiGateway::Method
ApiGateway::Method Integration
EDIT
This answer is not as verbose, and uses an http event instead of the Resources section. I haven't tested it, but it may also work for you.
Managed to achieve it with referring one function twice. But see also reply of Mike Patrick - looks more universal
...
events:
- http:
path: main/root/to/the/function
method: get
cors: true
- http:
path: /
method: get
integration: mock
request:
template:
application/json: '{"statusCode": 301}'
response:
template: redirect
headers:
Location: "'my-redirect-url-note-the-quotes-they-are-important'"
statusCodes:
301:
pattern: ''

Istio Authorization with JWT

I am running isio 1.0.2 and am unable to configure service authorization based on JWT claims against Azure AD.
I have succesfully configured and validated Azure AD oidc jwt end user authentication and it works fine.
Now I'd like to configure RBAC Authorization using request.auth.claims["preferred_username"] attribute.
I've created a ServiceRoleBinding like below:
apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRole
metadata:
name: service-reader
namespace: default
spec:
rules:
- services: ["myservice.default.svc.cluster.local"]
methods: ["GET"]
paths: ["*/products"]
---
apiVersion: "rbac.istio.io/v1alpha1"
kind: ServiceRoleBinding
metadata:
name: service-reader-binding
namespace: default
spec:
subjects:
- properties:
source.principal: "*"
request.auth.claims["preferred_username"]: "user#company.com"
roleRef:
kind: ServiceRole
name: "service-reader"
However, I keep getting 403 Forbidden from the service proxy, even though preferred_username claim from Authentication header is correct.
If I comment out request.auth.claims["preferred_username"]: "user#company.com" line the request succeeds.
Can anyone point me in the right direction regarding configuring authorization based on oidc and jwt?
Never mind. I found the problem.
I was missing user: "*" check to allow all users.
so under subjects it should say:
subjects:
- user: "*"
properties:
source.principal: "*"
request.auth.claims["preferred_username"]: "user#company.com"
That fixes it.