Open cobar79 opened 1 year ago
The commercial offer of Spring Cloud, Spring Cloud Services, uses OAuth2 to authorize the client to use the config server. You can find the code for how they do that here spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client
I suppose you could do something similar.
spring-cloud-services-starters looks to be a heavy port.
I was looking for a 3-5 story point way of overwriting the current RestTemplate logic.
Unfortunately, attempts of old school filter/interceptors did not work as the call was made before the filter/interceptors where instantiated. I guess I will look at extending the existing framework to override the basic authorization.
@ryanjbaxter Should I just close this? It was meant as a feature request. However, it doesn't look it is being considered.
I am not saying you need to port exactly what spring-cloud-services did I am just showing that it is possible to authorize the client before the request to the server is made, specifically I think this class is where that is done.
@kvmw am I correct?
@ryanjbaxter @cobar79 , You need to configure the RestTemplate in 2 different places :
Firstly, you need a BootstrapRegistryInitializer
. Since the call to config-server is done during the application startup you need to configure the RestTemplate before startup, during the bootstrapping using a BootstrapRegistryInitializer
. check ConfigClientOAuth2BootstrapRegistryInitializer in SCS Starters, for example. This has been mentioned in the Spring Cloud Config Docs too.
Also, if your client application consumes plain text or binary resources, you need to configure a ConfigResouceClient
as well. check ConfigResourceClientAutoConfiguration in SCS Starters, for example. You can ignore this configuration if you don't need to consume such resources.
The ConfigResourceClientAutoConfiguration which was mentioned above by @ryanjbaxter, is for the client applications with legacy processing enabled. This is the alternative for BootstrapRegistryInitializer
and you can ignore it if your application is developed recently and does not need legacy processing.
@ryanjbaxter ,
It would be nice if ConfigServerConfigDataLoader in Spring Cloud Config Client would provide an easier way to inject the RestTemplate. I don't know if alternative approach is even possible but the current one (using BootstrapRegistryInitializer
) is not the most straightforward and intuitive one.
I am not sure there would be an easier way because we are so early in the startup of the Boot application at the point we are loading configuration.
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
@ryanjbaxter @kvmw
Great discussion. However, this was just meant to be a feature request. As a Spring Developer, I have a requirement to lock down the config server, thus making it an OAuth2 resource server. Therefore, this requires the config server clients to authenticate with a Bearer token from the IDP. I would think this would be very common occurrence in the Spring Community?
As a proof of concept I cloned this project and with small changes to ConfigClientRequestTemplateFactory and ConfigClientProperties I was able to obtain a Bearer token from IDP and append it to the config server endpoint call.
We are investigating the commercial Cloud-Services-for-VMware-Tanzu product cost and portability to AWS. However, it would be more convenient and easier to maintain the additional feature to this project than it will be to implement the commercial version.
@cobar79 As I mentioned above the call to Config Server happens during bootstrap before any of those beans you mentioned above are even initialised. So changing those beans doesn't help.
Have a look at this code once again: https://github.com/spring-cloud/spring-cloud-config/blob/main/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLoader.java#L262
@kvmw Again, just proof of concept but it works:
The method below creates a Rest Template. I simply detect an OAuth2 property and use the Rest Template to get the Token from IDP and add it to the headers map which is added to the GenericRequestHeaderInterceptor. Later when the Config Server call is made, it uses the GenericRequestHeaderInterceptor adds the Bearer Token header in included in the call.
ConfigClientRequestTemplateFactory.create
String tokenUri = properties.getTokenUri();
if (tokenUri != null) {
String token = getOAuthToken(template, tokenUri);
headers.put(AUTHORIZATION, "Bearer " + token);
properties.setHeaders(headers);
}
injected just before adding the inteceptor
if (!headers.isEmpty()) {
template.setInterceptors(Arrays.asList(new GenericRequestHeaderInterceptor(headers)));
}
@cobar79
I see what you mean now. I was thinking your poc work is in your code rather than spring-cloud-config code.
I think that would work but not sure if the spring-config team would want that.
@ryanjbaxter would you consider a pull-request to support this requirement?
Absolutely!
Thanks @ryanjbaxter I will look over the guidelines and supporting documentation. I may need guidance moving from POC to PR since this will be my first open source contribution. Here are some areas or questions I have.
1) I replicated the spring.security.oauth2.client properties to fit into the spring.cloud.config properties. I wasn't crazy about duplicating the properties but given the impact of loading multiple properties and complexity of OAuth2 properties I think I would stick with updating ConfigClientProperties with OAuth2 properties. Even the commercial version flattened the OAuth2 properties into a single client registration.
spring:
cloud:
config:
token-uri: ${oauth.scheme}://localhost:${env.idp.port}/realms/${oauth.realm}/protocol/openid-connect/token
client-id:
I was planning on leaving the basic authentication properties as is and using username/password for both oauth2 and basic authentication. Thoughts?
spring:
cloud:
config:
username:
password:
token:
2) Client/User credential encryption: Most cyber teams won't allow plain text credentials in property files. That leaves environment/system properties or encrypting the credentials. I assume the Spring team would just want to go with environment/system properties for this? In the POC, I actually incorporated a Jayspt SimplePBEStringEncryptor. I passed in the algorithm and iterations via the properties and set the encryption password as an environment variable.
spring:
cloud:
config:
token-uri:
client-id: ENC(blablabla)
client-secret: ENC(yaddaYaddaYadda)
username: ENC()
password: ENC()
encryptor-algorithm: PBEWITHHMACSHA512ANDAES_256
encryptor-iterations: 1000
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot</artifactId>
<version>3.0.5</version>
</dependency>
We love first time contributors ❤️
That sounds fine to me
We have the ability to encrypt and decrypt properties as part of Spring Cloud Config. Have you looked into leveraging that functionality?
https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_encryption_and_decryption
Thanks Ryan.
I have it mostly stubbed out with JCE. I am having issues with JCE cypher properties in the Config Client application. I can't seem to find any documentation on using JCE in a normal Boot application. Do you happen to know where I can find documentation?
Removed old Jasypt encryption and attempted to try simple Symmetric encryption first.
Property: spring.cloud.config.client-secret
Value: "{cipher}cdac3c554d3f6fbdc6f2f553a7b3b3e1b64b1fbb3b1c8fa5b2766be4dafc0b6a6ca38e13442787f5b779dbbd3634001f7a89d030ad8f0363c61a7719d425ef21"
Origin: URL [file:config/application-local.yml] - 78:22
Reason: java.lang.UnsupportedOperationException: No decryption for FailsafeTextEncryptor. Did you configure the keystore correctly?
This seems to say you can't do Symmetric Encryption, but thought I would ask.
@ryanjbaxter Ready for PR. How do I obtain access to push feature branch and create PR?
I went with Jasypt encryption since I couldn't find supporting documentation of JCE support for the client boot application. See comment above.
@kvmw Can you help me with getting access to push my branch and create a PR?
@cobar79 I don't have admin access to the repo. You need to fork the repo and submit the PR from your fork.
You can configure encryption using symetric or asymmetric encryption https://docs.spring.io/spring-cloud-config/docs/4.0.4/reference/html/#_key_management
Ultimately that shouldn't really matter I believe since the user will configure encryption however they want to encrypt the secret right?
As far as the PR goes, fork the repo, create a branch from the 4.0.x branch, then push that branch to your forked repo and create a PR from that branch against the 4.0.x branch in this repo on GitHub.
@ryanjbaxter Since the config server logic is way before spring boot sequence, there is no encryption bean created yet. So the properties loaded in the ConfigClientProperties were encrypted.
I can try the JCE again, but I had issues with the cipher in the properties. It failed with the cipher unquoted for yaml syntax and failed for "No decryption for FailsafeTextEncryptor. Did you configure the keystore correctly?" when quoted.
I went with Jayspt since it is the most common practice for encrypting properties in Spring applications.
@ryanjbaxter do you have any idea on the timeline for this feature? My team is really interested in this feature and we are trying to determine if we should wait for official support or implement a custom solution.
It won't be for a while since we need to wait for a major release of Spring Cloud where we can introduce major changes like this.
Hello,
Is there a roadmap or timeline for spring cloud?
Regards
We are working on the details of the next major now. There will be announcements forthcoming.
@ryanjbaxter In case you need a sample implementation, I've updated the SCS Starters to support the OAuth2 outside CF/TAS env. In the earlier releases oauth2 support was limited to TAS/CF environment.
Here is the main class involved: https://github.com/pivotal-cf/spring-cloud-services-starters/blob/main/spring-cloud-services-config-client-autoconfigure/src/main/java/io/pivotal/spring/cloud/config/client/OAuth2ConfigDataLocationResolver.java
@ryanjbaxter Hi there! Have there been any announcements? When could we expect this issue to be deployed?
Our next major is currently planned for November of 2025 https://spring.io/blog/2024/10/01/from-spring-framework-6-2-to-7-0
Hello @ryanjbaxter , does this mean that we have a year time to get the changes merged into main?
Not necessarily, it means it won't be GA for a year. The changes can be merged as soon as we branch for the next major. I don't have a specific date for when that will occur though.
I would like to implement OAuth2 resource server in the Spring Config Server and require JWT for all configuration requests.
Is your feature request related to a problem? Please describe.
I can't seem to find any method to implement a Spring Security OAuth2 Client Provider to the Spring Cloud Client call. Overriding ConfigServicePropertySourceLocator
177 and Adding Generic "Authorization" support for Config Client do not appear to work with current import connection framework
"optional:configserver:http://${env.config.hostname}:${env.config.port}/config-server"
The call to the Configuration Server is made before the ConfigServicePropertySourceLocator is instantiated.
Describe the solution you'd like I would like to use the same implementation used for Machine to Machine OAuth2 communication where by the Spring Security handles obtaining and refreshing the Bearer Token to be passed to the Spring Config Server calls on startup. Preferably a WebClient over RestTemplate solution.
Describe alternatives you've considered A method to intercept the Spring Config Server rest call, call the IDP and include the Bearer Token manually, there by bypassing the current RestTemplate and any Basic Authentication.
Additional context Spring Boot 3.1.2, Spring Security 6.1.2 Keycloak OAuth2 implementation
Configuration Server Security