Reputation: 77
Short description
I am creating whole environment for some microservices. I have REST API written in Java with spring boot. I am also using keycloak as identity provider. I put them both in kubernetes cluster (minikube) and set up ingress to provide external access. To expose ingress as localhost I am using "minikube tunnel". The problem is, every JWT obtained from keycloak through ingress has included issuer with ingress external adress instead of service name that is mapped in ingress resource. In Spring boot application.properties I added jwt-issuer with the name of internal service exposing keycloak deployment. With JWT created in that way I am always getting error "The iss claim is not valid".
Requests flow
Configuration and code
application.properties
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://keycloak-service.default/realms/cryptoservices
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak-service.default/realms/cryptoservices/protoco l/openid-connect/certs
SecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http.oauth2ResourceServer().jwt();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
http.authorizeHttpRequests().requestMatchers("/api/v1/generators/**").hasRole("GeneratorsUser");
http.authorizeHttpRequests().requestMatchers("/api/v1/profiles/**").hasRole("GeneratorsProfilesUser");
http.authorizeHttpRequests().anyRequest().denyAll();
return http.build();
}
}
keycloak.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
namespace: default
labels:
app: keycloak
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: my_keycloak:latest
command: ["/opt/keycloak/bin/kc.sh"]
args: ["start-dev", "--import-realm"]
imagePullPolicy: Never
ports:
- containerPort: 8080
env:
- name: KEYCLOAK_ADMIN
value: admin
- name: KEYCLOAK_ADMIN_PASSWORD
value: admin
- name: KEYCLOAK_PROXY_ADDRESS_FORWARDING
value: "true"
volumeMounts:
- name: keycloak-volume
mountPath: /opt/keycloak/data/import
volumes:
- name: keycloak-volume
configMap:
name: keycloak-configmap
---
apiVersion: v1
kind: Service
metadata:
name: keycloak-service
spec:
selector:
app: keycloak
ports:
- port: 80
targetPort: 8080
protocol: TCP
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: keycloak-ingress
namespace: default
annotations:
kubernetes.io/ingress.class: kong
spec:
rules:
- host: localhost
http:
paths:
- path: /admin(/|$)(.*)
pathType: Prefix
backend:
service:
name: keycloak-service
port:
number: 80
- path: /auth
pathType: Prefix
backend:
service:
name: keycloak-service
port:
number: 80
- path: /resources(/|$)(.*)
pathType: Prefix
backend:
service:
name: keycloak-service
port:
number: 80
- path: /realms(/|$)(.*)
pathType: Prefix
backend:
service:
name: keycloak-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cryptogenerator-ingress
namespace: generation-service
annotations:
kubernetes.io/ingress.class: kong
spec:
rules:
- host: localhost
http:
paths:
- path: /cryptogenerator(/|$)(.*)
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
Question
Is there a way to force keycloak to use different issuer when generating JWT? I tried to add X-Forwared headers in ingress and tried to use different flags related to hostname when running keycloak --hostname=, --hostname-strict=false, --proxy edge. Both tries failed.
Or is there any more suitable way to achive similar flow where there is no frontend (besides admin page) and you can easily obtain token from inside kubernetes and then use it to authorize requests for application?
Additional information
Keycloak version: 21.1.1, based on image quay.io/keycloak/keycloak
Upvotes: 1
Views: 2260
Reputation: 12774
The value of spring.security.oauth2.resourceserver.jwt.issuer-uri
must match exactly the value of tokens iss
claim (even trailing /
, if any is important).
Two options for you:
spring.security.oauth2.resourceserver.jwt.issuer-uri
and spring.security.oauth2.resourceserver.jwt.jwk-set-uri
: only the host in jwk-set-uri
has to be reachable by resource servers. Spring tries to use issuer-uri
to fetch OpenID config and then download JWK-set only when jwk-set-uri
is not explicitly providedhostname
in Keycloak configuration to match the name with which the service can be reached from the outside of the cluster (this will impact the iss
claim), but using something else than loclahost
which has a different meaning inside pods and on the host system. You may add an entry for the host machine name in pods hosts file (instead of using localhost
).Upvotes: 4