how to Disable Schema Introspection in graphql-spqr-spring-boot-starter - spring-boot

I have integrated my spring boot application with graphql-spqr-spring-boot-starter https://github.com/leangen/graphql-spqr-spring-boot-starter , I need to find a way on how to disable graphql schema introspection since its a security issue for production.

I am using graphql-spqr 0.9.9 and graphql-spqr-spring-boot-starter 0.0.4, but the code base changed for graphql-spqr 0.10. I'll try to cover both cases, but keep in mind you might have to tweak the code snippets a bit.
In Graphql-spqr-spring-boot starter, GraphQLSchemaGenerator is a bean used to generate the GraphQSchema. It is defined in io.leangen.graphql.spqr.spring.autoconfigure.BaseAutoConfiguration (v0.10) or io.leangen.graphql.spqr.spring.autoconfigure.SpqrAutoConfiguration (v0.9).
You need to provide your own GraphQLSchemaGenerator bean that will set the GraphqlFieldVisibility for the introspection query. According to this issue (cached by google: https://webcache.googleusercontent.com/search?q=cache:8VV29F3ovZsJ:https://github.com/leangen/graphql-spqr/issues/305), there are two different ways to set the field visibility:
Graphql-spqr 0.9
#Bean
public GraphQLSchemaGenerator graphQLSchemaGenerator(SpqrProperties spqrProperties) {
GraphQLSchemaGenerator schemaGenerator = new GraphQLSchemaGenerator();
schemaGenerator.withSchemaProcessors((schemaBuilder, buildContext) ->
{
schemaBuilder.fieldVisibility(new NoIntrospectionGraphqlFieldVisibility());
return schemaBuilder;
});
//Other GraphQLSchemaGenerator configuration
}
Graphql-spqr 0.10
#Bean
public GraphQLSchemaGenerator graphQLSchemaGenerator(SpqrProperties spqrProperties) {
GraphQLSchemaGenerator schemaGenerator = new GraphQLSchemaGenerator();
schemaGenerator.withSchemaProcessors((schemaBuilder, buildContext) ->
{
buildContext.codeRegistry.fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY);
return schemaBuilder;
});
//Other GraphQLSchemaGenerator configuration
}
You can get inspiration from the default implementation to set the GraphQLGenerator properly.

This seems to work, there is a bean in SpqrAutoConfiguration class to generateGraphql schema from the generator object
#Bean
public GraphQLSchema graphQLSchema(GraphQLSchemaGenerator schemaGenerator) {
schemaGenerator.withSchemaProcessors((schemaBuilder, buildContext) ->
{
schemaBuilder.fieldVisibility(new NoIntrospectionGraphqlFieldVisibility());
return schemaBuilder;
});
return schemaGenerator.generate();
}

schemaBuilder.fieldVisibility is Deprecated.
Graphql-spqr 0.10
#Bean
public GraphQLSchema graphQLSchema(GraphQLSchemaGenerator schemaGenerator) {
schemaGenerator.withSchemaProcessors((schemaBuilder, buildContext) -> {
schemaBuilder.codeRegistry(
buildContext
.codeRegistry
.fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY)
.build()
);
return schemaBuilder;
});
return schemaGenerator.generate();
}

Related

How to exclude some uri to be observed using springboot3/micrometer

Hy
I am using springboot 3 with the new micrometer observation. Is there a way to prevent generating a trace_id/span_id for some paths like /actuator/prometheus? Observation add a trace id for each call to /actuator/*.
Thank you
You need to give more information about a problem, but i think you have set this line in application.propeties like:
management.endpoints.web.exposure.include=/actuator/*
But exists option like:
management.endpoints.web.exposure.exclude=/actuator/prometheus
I managed to find a half solution to the problem, by defining the ObservationRegistry this way:
#Bean
#ConditionalOnMissingBean
ObservationRegistry observationRegistry() {
PathMatcher pathMatcher = new AntPathMatcher("/");
ObservationRegistry observationRegistry = ObservationRegistry.create();
observationRegistry.observationConfig().observationPredicate((name, context) -> {
if(context instanceof ServerRequestObservationContext) {
return !pathMatcher.match("/actuator/**", ((ServerRequestObservationContext) context).getCarrier().getRequestURI());
} else {
return true;
}
});
return observationRegistry;
}
This doesn't completely ignore the actuator requests, only the first span. So if you have for example Spring Security on your classpath, those spans are left intact.
EDIT: Looks like you don't need to redefine the entire observation registry, you can use a bean like the one show here: https://docs.spring.io/spring-security/reference/servlet/integrations/observability.html
EDIT2: From what I can tell, you need to include these 2 beans, and no actuator call will be traced (completely disables tracing for spring security too):
#Bean
ObservationRegistryCustomizer<ObservationRegistry> skipActuatorEndpointsFromObservation() {
PathMatcher pathMatcher = new AntPathMatcher("/");
return (registry) -> registry.observationConfig().observationPredicate((name, context) -> {
if (context instanceof ServerRequestObservationContext observationContext) {
return !pathMatcher.match("/actuator/**", observationContext.getCarrier().getRequestURI());
} else {
return true;
}
});
}
#Bean
ObservationRegistryCustomizer<ObservationRegistry> skipSecuritySpansFromObservation() {
return (registry) -> registry.observationConfig().observationPredicate((name, context) ->
!name.startsWith("spring.security"));
}
Also, you might want to keep an eye out on this issue: https://github.com/spring-projects/spring-framework/issues/29210

WebServerFactoryCustomizer is not hit in a Springboot Webflux app

Following Configure the Web Server , I add a NettyWebServerFactoryCustomizer
#Configuration
public class NettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
#Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.addServerCustomizers(httpServer -> {
return httpServer
.wiretap(true)
.metrics(true, s->s)
.doOnConnection(conn -> {
conn.addHandlerFirst(new ReadTimeoutHandler(50, TimeUnit.MILLISECONDS));
});
});
}
}
I have two questions:
When I run the app, the customize function is not hit. Where do I miss?
My purpose is to enable the Netty metrics, I can't find any documents about config the metrics in the application.yml file. so I add the NettyWebServerFactoryCustomizer.
The second parameter of .metrics(true, s->s) is a uriTagValue, Are there any example about how to pass in value? I just use s->s because I refer this, but this maybe can't avoid cardinality explosion, Are there any function like ServerWebExchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE) simple give us the templated URL?
I found the workaround of the question 1: define a bean instead of implementing WebServerFactoryCustomizer
#Bean
public ReactiveWebServerFactory reactiveWebServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(builder -> builder
.wiretap(true)
.metrics(true,s->s)
.accessLog(true)
.doOnConnection(conn -> {
conn.addHandlerFirst(new ReadTimeoutHandler(50, TimeUnit.MILLISECONDS));
}));
return factory;
}
About your question 2 : The second parameter of .metrics(true, s->s) is a uriTagValue, Are there any example about how to pass in value?
private static final Pattern URI_TEMPLATE_PATTERN = Pattern.compile("/test/.*");
#Bean
public ReactiveWebServerFactory reactiveWebServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(builder -> builder
.wiretap(true)
.metrics(true,
uriValue ->
{
Matcher matcher = URI_TEMPLATE_PATTERN .matcher(uriValue);
if (matcher.matches()) {
return "/test/";
}
return "/";
}
.accessLog(true)
.doOnConnection(conn -> {
conn.addHandlerFirst(new ReadTimeoutHandler(50, TimeUnit.MILLISECONDS));
}));
return factory;
}

Autowiring MongoClient and MongoClientSettings without explicitly specifying a Connection String

I am upgrading the MongoDB driver which requires moving away from the older MongoClientOptions to the newer MongoClientSettings.
In the older implementation, the following configuration was used within a #Configuration class with the ConnectionString inferred from the spring.data.mongodb.uri and an #Autowired MongoTemplate:
#Bean
public MongoClientOptions mongoOptions() {
Builder clientOptionsBuilder = MongoClientOptions.builder()
//Timeout Configurations
if(sslIsEnabled) {
clientOptionsBuilder.sslEnabled(true)
//Other SSL options
}
return clientOptionsBuilder.build();
}
And in the Newer Implementation, a ConnectionString parameter is specifically expected, and the property file spring.data.mongodb.uri is not selected automatically. As a result, I have specified the connection string using the #Value Annotation. Not doing this results in the program to infer localhost:27017 as the connection source.
#Value("${spring.data.mongodb.uri}")
String connectionString;
#Bean
public MongoClient mongoClient() {
MongoClientSettings.Builder clientSettingsBuilder = MongoClientSettings.builder()
.applyToSocketSettings(builder -> {
// Timeout Configurations
}).applyConnectionString(new ConnectionString(connectionString));
if (sSLEnabled) {
clientSettingsBuilder.applyToSslSettings(builder -> {
builder.enabled(sslIsEnabled);
//Other SSL Settings
});
}
return MongoClients.create(clientSettingsBuilder.build());
}
While documentation and other StackOverflow posts mention MongoClientSettings overrides the property file entries, is there a way to retrieve/infer the MongoClientSettings from the property files and then append other custom configurations to it?
I am using Spring Boot 2.6 and spring starter dependency for MongoDB
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
I found that a similar question asked on GitHub earlier.
Modify MongoClientSettings while using auto configuration with mongodb #20195
Replacing the #Bean configuration which had MongoClientOptions with MongoClientSettingsBuilderCustomizer helped solve the problem.
#Bean
public MongoClientSettingsBuilderCustomizer mongoDBDefaultSettings()
throws KeyManagementException, NoSuchAlgorithmException {
return builder -> {
builder.applyToSocketSettings(bldr -> {
//Apply any custom socket settings
});
builder.applyToSslSettings(blockBuilder -> {
// Apply SSL settings
});
// Apply other settings to the builder.
};
}

Togglz - Username Activation Strategy Implementation

I am trying to implement UsernameActivation Startegy in Springboot using togglz, but due to insufficient example/documentation on this, I am unable to do so. It is a simple Poc in maven. Here are my classes:
public enum Features implements Feature{
#Label("just a description")
#EnabledByDefault
HELLO_WORLD,
#Label("Hello World Feature")
#DefaultActivationStrategy(id = UsernameActivationStrategy.ID, parameters =
{#ActivationParameter(name = UsernameActivationStrategy.PARAM_USERS, value = "suga")
})
HELLO,
#Label("another descrition")
#EnabledByDefault
REVERSE_GREETING;
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
}
#Component
public class Togglz implements TogglzConfig {
public Class<? extends Feature> getFeatureClass() {
return Features.class;
}
public StateRepository getStateRepository() {
return new FileBasedStateRepository(new File("/tmp/features.properties"));
}
public UserProvider getUserProvider() {
return new SpringSecurityUserProvider("ADMIN_ROLE");
}
}
I want to use UsernameActivation strategy but i am not sure what more code changes I need to do in order for it to work. I do know that it is somehow related to UserProvider. Also, I am not sure how will it compare the username value and how will it capture the current user value. Any idea around this will be of great help!
I had to override the getUserProvider method. Since I am using Spring for auto-configuration, and not extending ToggleConfig, I added this as a bean to load at startup.
#Bean
public UserProvider getUserProvider() {
return new UserProvider() {
#Override
public FeatureUser getCurrentUser() {
String username = <MyAppSecurityProvider>.getUserName();
boolean isAdmin = "admin".equals(username);
return new SimpleFeatureUser(username, isAdmin);
}
};
}
Note: I had to do this as my app uses our inbuilt security mechanism. Reading the documentation, it looks like its easier if you are using standard security such as Spring or Servlet.
My config in application.yml(the same thing you have in the annotation)
togglz:
features:
FRIST_FEATURE:
enabled: true
strategy: username
param:
users: user1,user2
SECOND_FEATURE:
enabled: true
strategy: username
param:
users: user2,user3

Springdoc GroupedOpenApi not following global parameters set with OperationCustomizer

When using GroupedOpenApi to define an API group, the common set of parameters that are added to every endpoint is not present in the parameters list.
Below are the respective codes
#Bean
public GroupedOpenApi v1Apis() {
return GroupedOpenApi.builder().group("v1 APIs")
// hide all v2 APIs
.pathsToExclude("/api/v2/**", "/v2/**")
// show all v1 APIs
.pathsToMatch("/api/v1/**", "/v1/**")
.build();
}
And the class to add the Standard Headers to all the endpoints
#Component
public class GlobalHeaderAdder implements OperationCustomizer {
#Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
operation.addParametersItem(new Parameter().$ref("#/components/parameters/ClientID"));
operation.addSecurityItem(new SecurityRequirement().addList("Authorization"));
List<Parameter> parameterList = operation.getParameters();
if (parameterList!=null && !parameterList.isEmpty()) {
Collections.rotate(parameterList, 1);
}
return operation;
}
}
Actual Output
Expected Output
Workaround
Adding the paths to be included/excluded in the application properties file solves the error. But something at the code level will be much appreciated.
Attach the required OperationCustomizerobject while building the Api Group.
#Bean
public GroupedOpenApi v1Apis(GlobalHeaderAdder globalHeaderAdder) {
return GroupedOpenApi.builder().group("v1 APIs")
// hide all v2 APIs
.pathsToExclude("/api/v2/**", "/v2/**")
// show all v1 APIs
.pathsToMatch("/api/v1/**", "/v1/**")
.addOperationCustomizer(globalHeaderAdded)
.build();
}
Edit: Answer updated with reference to #Value not providing values from application properties Spring Boot
Alternative to add and load OperationCustomizer in the case you declare yours open api groups by properties springdoc.group-configs[0].group= instead definition by Java code in a Spring Configuration GroupedOpenApi.builder().
#Bean
public Map<String, GroupedOpenApi> configureGroupedsOpenApi(Map<String, GroupedOpenApi> groupedsOpenApi, OperationCustomizer operationCustomizer) {
groupedsOpenApi.forEach((id, groupedOpenApi) -> groupedOpenApi.getOperationCustomizers()
.add(operationCustomizer));
return groupedsOpenApi;
}

Resources