How to modify the Zuul ServiceId at Runtime based on request param? - netflix-eureka

How to achieve change servciceId based on request params ??
Below is our Zuul Config
zuul:
host:
connect-timeout-millis: 200000
connection-request-timeout-millis: 200000
socket-timeout-millis: 200000
ignored-services: "*"
routes:
route-1:
path: /path1/**
serviceId: ServiceA
route-2:
path: /path2/**
serviceId: ServiceB
Over here we are selecting serviceId based on path1/path2.
if http://localhost:8050/path1/endpointPath?requestParam=ParamValue1 this should call serviceA
if http://localhost:8050/path1/endpointPath?requestParam=ParamValue2 this should call serviceB

was able to achieve this by using route filter and config changes
Config :
routes:
route-1:
path: /**
serviceId: ServiceA
stripPrefix: true
RouteFilter:
Optional<String> parameter = Optional.ofNullable(ctx.getRequest().getParameter("requestparam"));
if (parameter.isPresent()) {
if (parameter.get().equalsIgnoreCase("ValueA")) {
ctx.set("serviceId", "ServiceA");
} else {
ctx.set("serviceId", "ServiceB");
}
}
Is this fine or do we have any simpler way to achieve ?
Here is there any we can limit not to define serviceId in properties file ?

Related

Request-based Sticky Session configuration with Spring Cloud LoadBalancer

I have the following configuration for request-based sticky session using Spring Cloud LoadBalancer
spring:
cloud:
discovery.client.simple.instances:
say-hello:
- instanceId: say-hello1
uri: http://localhost:8080
- instanceId: say-hello2
uri: http://localhost:8081
loadbalancer:
configurations: request-based-sticky-session
sticky-session:
add-service-instance-cookie: true
server.port:9090
the following call:
$ http :9090/hi 'Cookie:sc-lb-instance-id=say-hello1'
should go only to the say-hello1 instance based on the Request-based Sticky Session for LoadBalancer but instead is using round robin load balancing.
What do I miss here?
Here is the source code to try it: https://github.com/altfatterz/client-side-loadbalancing
There are 2 things to consider here:
In the sample, the cookie has to be passed on to the actual load-balanced request, for example like so:
#GetMapping("/hi")
public String hi(#RequestParam(value = "name", defaultValue = "Mary") String name) {
logger.info("Accessing /hi endpoint");
HttpHeaders headers = new HttpHeaders();
headers.set("Cookie", "sc-lb-instance-id=say-hello1");
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<String> greeting = restTemplate.exchange("http://say-hello/greeting", HttpMethod.GET, entity, String.class, new HashMap<>());
return greeting + " " + name;
}
This feature is only supported for WebClient-backed load-balancing. It was not properly documented. I have documented it here.
I have also created a GitHub issue for adding the non-reactive implementation, however, the decision to implement it will be dependant on larger community interest.

Dynamic header based routing with fallback

I would like to route traffic to pods based on headers - with a fallback.
The desired result would be a k8s cluster where multiple versions of the same service could be deployed and routed to using header values.
svcA
svcB
svcC
each of these services (the main branch of git repo) would be deployed either to default namespace or labelled 'main'. any feature branch of each service can also be deployed, either into its own namespace or labelled with the branch name.
Ideally by setting a header X-svcA to a value matching a branch name, we would route any traffic to the in matching namespace or label. If there is no such name space or label, route the traffic to the default (main) pod.
if HEADERX && svcX:label
route->svcX:label
else
route->svcX
The first question - is this (or something like) even possible with istio or linkerd
You can do that using Istio VirtualService
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
...
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
Read more here.
Yes you can rout the request based on a header with Istion & Linkerd
For istio there is nice article : https://dwdraju.medium.com/simplified-header-based-routing-with-istio-for-http-grpc-traffic-ff9be55f83ca
in istio's virtual service you can update the header like :
http:
- match:
- headers:
x-svc-env:
regex: v2
For linkerd :
Kind = "service-router"
Name = "service"
Routes = [
{
Match {
HTTP {
PathPrefix = "/api/service/com.example.com.PingService"
}
}
Destination {
Service = "pinging"
},
},
{
Match {
HTTP {
PathPrefix = "/api/service/com.example.com.PingService"
Header = [
{
Name = "x-version"
Exact = "2"
},
]
}
}
Destination {
Service = "pinging"
ServiceSubset = "v2"
},
}

Deny access to one particular subpath for spring cloud gateway route

We're using Spring Cloud Gateway in front of our backend services. We have a route similar to the following:
routes:
- id: foobar-service
uri: lb://foobar-service
predicates:
- Path=/foobar/**
filters:
- StripPrefix=1
We want to deny access to one particular subpath (e.g. /foobar/baz/**) but leave the rest open. Is it possible to do this using the YAML syntax? Perhaps we need to implement the routes using the Fluent API instead?
routes:
- id: foobar-baz-service
uri: no://op
predicates:
- Path=/foobar/baz/**
filters:
- SetStatus=403
- id: foobar-service
uri: lb://foobar-service
predicates:
- Path=/foobar/**
filters:
- StripPrefix=1
Sharing my experience with the API implementation with a single route.
#Bean
public RouteLocator routes( final RouteLocatorBuilder locatorBuilder ) {
RouteLocatorBuilder.Builder builder = locatorBuilder.routes();
builder.route(p -> p //
.path(getAllowedPaths()) //
.and() //
.not(n -> n.path(getRestrictedPaths()) //
.filters(f -> f //
//filters
.uri(uri)));
return builder.build();
}

Custom WebserviceUserProvider and FOSAuthServerBundle

I'm using an SF2.8 API based on this tutorial https://gist.github.com/tjamps/11d617a4b318d65ca583 except i'm using MongoDB.
When a FOSUser (created on my DB A with oAuth tables) is connected to my API, i don't have all infos what i need because there are an other database.
I found a SF doc which explain how to custom symfony default authentication by replacing the default FOS User provider with a custom user provider.
So I decided to create the same architecture on this doc : http://symfony.com/doc/2.8/security/custom_provider.html
Before asking my oAuth token in /oauth/v2/token by HTTP Post request, my overrided loadUserByUsername method call an external API and instanciate a WebserviceUser which contain a user companies's collection in addition to having basic fields like username, password, salt etc. needed to connect.
After loadUserByUsername call, oAuth try to flush the generated accesstoken to the connected user into DB, but an exception is thrown because the User document to persist in AccessToken document is a AppBundle\Security\User\WebserviceUser instead of AppBundle\Document\User (child of FOSUser Document) so the mapping fail while persisting.
I got this :
The class 'AppBundle\Security\User\WebserviceUser' was not found in the chain configured namespaces AppBundle\Document, FOS\UserBundle\Document, FOS\OAuthServerBundle\Document (500 Internal Server Error)
Did I do something wrong ?
EDIT : My new loadUserByUsername method :
public function loadUserByUsername($username)
{
$apiUser = $this->manager->getRepository('AppBundle:User')->findOneByUsername($username);
if (is_null($apiUser)) {
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
$userData = $this->client->httpGetList("user", ['filter_usrMail' => $apiUser->getEmail()]);
$userData = $userData[array_keys($userData)[0]];
$companies = isset($userData['usrCompany']) ? $userData['usrCompany'] : [];
if ($userData) {
$role = isset($userData['usrRole']) ? [$userData['usrRole']] : ['ROLE_USER'];
$user = new WebserviceUser($apiUser->getUsername(), $apiUser->getPassword(), $apiUser->getSalt(), $apiUser->getRoles());
$user->setCompanies($companies);
$user->setApiUsername($apiUser->getUsername());
return $user;
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
fos_oauth_server yml conf :
db_driver: mongodb
client_class: AppBundle\Document\Client
access_token_class: AppBundle\Document\AccessToken
refresh_token_class: AppBundle\Document\RefreshToken
auth_code_class: AppBundle\Document\AuthCode
service:
user_provider: app.webservice_user_provider
services.yml conf :
app.webservice_user_provider:
class: AppBundle\Security\User\WebserviceUserProvider
arguments: ['#my_communicator.client', '#session', '#doctrine.odm.mongodb.document_manager', '%session_refresh_ttl%']
security.yml conf :
encoders:
AppBundle\Security\User\WebserviceUser: bcrypt
FOS\UserBundle\Model\UserInterface: bcrypt
providers:
webservice:
id: app.webservice_user_provider
#in_memory:
# memory: ~
fos_userbundle:
id: fos_user.user_provider.username
role_hierarchy:
ROLE_ADMIN: ROLE_USER
firewalls:
oauth_token: # Everyone can access the access token URL.
pattern: ^/oauth/v2/token
security: false
api:
pattern: ^\/api(?!\/doc|\/v[0-9][\.0-9]*\/core(\/createaccount|\/clients)) # All URLs are protected (except api doc and api create account)
fos_oauth: true # OAuth2 protected resource
stateless: true # Do no set session cookies
anonymous: false # Anonymous access is not allowed
access_denied_handler: app.listener.access_denied.handler
apidoc:
pattern: ^\/api\/doc
anonymous: false
security: false
access_control:
- { path: ^\/api\/v[0-9][\.0-9]*\/(?!(admin|core)), roles: ROLE_USER }
- { path: ^\/api\/v[0-9][\.0-9]*\/(admin|core)(?!(\/createaccount|\/clients)), roles: ROLE_ADMIN }
- { path: ^\/api\/v[0-9][\.0-9]*\/core(\/createaccount|\/clients), roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, localhost, ::1] }
- { path: ^\/api\/v[0-9][\.0-9]*\/core(\/createaccount|\/clients), roles: ROLE_NO_ACCESS }
access_decision_manager:
strategy: unanimous

Zuul Routing on Root Path

I want to config zuul to route request to root / to a home page. I tried:
root:
path: /
url: http://hostname/home/index.jsp
and
root:
path: /**
url: http://hostname/home/index.jsp
But neither of them works. I just got a 404 NOT FOUND. I think the path match config should be similar to those with contexts, such as /service/**, but it's not.
This is what I have done to make this work.
Within Zuul -> controller:
#RequestMapping(value = "/", method = RequestMethod.GET)
public String handleRequest() {
return "forward:/ux/";
}
Zuul Properties:
zuul:
addProxyHeaders: true
routes:
example-ux:
path: /ux/**
stripPrefix: false
Within example-ux Service properties:
server:
servlet-path: /*
context-path: /ux
This configuration also solves the problem of static resources resolution. i.e. /static/css static/js etc...