diff --git a/build-bin/docker-compose-zipkin-eureka.yml b/build-bin/docker-compose-zipkin-eureka.yml new file mode 100755 index 00000000000..55634f44f64 --- /dev/null +++ b/build-bin/docker-compose-zipkin-eureka.yml @@ -0,0 +1,32 @@ +# +# Copyright 2015-2024 The OpenZipkin Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +# uses 2.4 so we can use condition: service_healthy +version: "2.4" + +# Test both authenticated and unauthenticated, as if there is a Spring problem, +# the latter will crash. We only need to use HEALTHCHECK for this. +services: + eureka: + image: openzipkin/zipkin-eureka:test + container_name: eureka + sut: + image: openzipkin/zipkin-eureka:test + container_name: sut + environment: + EUREKA_USERNAME: testuser + EUREKA_PASSWORD: testpassword + depends_on: + eureka: + condition: service_healthy diff --git a/docker/test-images/zipkin-eureka/README.md b/docker/test-images/zipkin-eureka/README.md index e9cec0d557e..8b42358f734 100644 --- a/docker/test-images/zipkin-eureka/README.md +++ b/docker/test-images/zipkin-eureka/README.md @@ -3,10 +3,15 @@ The `zipkin-eureka` testing image runs Eureka Server for service discovery integration of the Zipkin server. This listens on port 8761. +Besides norms defined in [docker-java](https://github.com/openzipkin/docker-java), this accepts the +following environment variables: + +* `EUREKA_USERNAME`: username for authenticating endpoints under "/eureka". +* `EUREKA_PASSWORD`: password for authenticating endpoints under "/eureka". +* `JAVA_OPTS`: to change settings such as heap size for Eureka. + To build `openzipkin/zipkin-eureka:test`, from the top-level of the repository, run: ```bash $ DOCKER_FILE=docker/test-images/zipkin-eureka/Dockerfile build-bin/docker/docker_build openzipkin/zipkin-eureka:test $ docker run -p 8761:8761 --rm openzipkin/zipkin-eureka:test ``` - -You can use the env variable `JAVA_OPTS` to change settings such as heap size for Eureka. diff --git a/docker/test-images/zipkin-eureka/pom.xml b/docker/test-images/zipkin-eureka/pom.xml index 17fb74ae0bc..f74b48da2f4 100644 --- a/docker/test-images/zipkin-eureka/pom.xml +++ b/docker/test-images/zipkin-eureka/pom.xml @@ -50,15 +50,6 @@ spring-cloud-starter-netflix-eureka-server - - org.springframework.security - spring-security-web - - - org.springframework.security - spring-security-config - - org.springframework.boot diff --git a/docker/test-images/zipkin-eureka/src/main/java/zipkin/test/EurekaSecurity.java b/docker/test-images/zipkin-eureka/src/main/java/zipkin/test/EurekaSecurity.java index 8f032ef9de6..0b7400ae6dc 100644 --- a/docker/test-images/zipkin-eureka/src/main/java/zipkin/test/EurekaSecurity.java +++ b/docker/test-images/zipkin-eureka/src/main/java/zipkin/test/EurekaSecurity.java @@ -13,46 +13,52 @@ */ package zipkin.test; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Base64; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.filter.OncePerRequestFilter; -import static org.springframework.security.crypto.factory.PasswordEncoderFactories.createDelegatingPasswordEncoder; +import static java.nio.charset.StandardCharsets.UTF_8; /** This enables security, particularly only BASIC auth, when {@code EUREKA_USERNAME} is set. */ @Configuration @ConditionalOnProperty("eureka.username") @EnableConfigurationProperties(EurekaProperties.class) -@ImportAutoConfiguration(SecurityAutoConfiguration.class) public class EurekaSecurity { - @Bean InMemoryUserDetailsManager userDetailsService(EurekaProperties props) { - PasswordEncoder encoder = createDelegatingPasswordEncoder(); - UserDetails user = User.withUsername(props.getUsername()) - .password(encoder.encode(props.getPassword())) - .roles("ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); + @Bean FilterRegistrationBean authFilter(EurekaProperties props) { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new BasicAuthFilter(props.getUsername(), props.getPassword())); + registrationBean.addUrlPatterns("/eureka/*"); // Auth /eureka, though only v2 is valid + registrationBean.setOrder(2); + return registrationBean; } - /** - * You have to disable CSRF to allow BASIC authenticating Eureka clients to operate. - *

- * See Securing The Eureka Server - */ - @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.csrf(csrf -> csrf.ignoringRequestMatchers("/eureka/**")); - http.authorizeHttpRequests(authz -> authz.requestMatchers("/eureka/**").authenticated()) - .httpBasic(Customizer.withDefaults()); - return http.build(); + /** Implements BASIC instead of spring-security + CORS, CSRF and management exclusions. */ + static final class BasicAuthFilter extends OncePerRequestFilter { + final String expectedAuthorization; + + BasicAuthFilter(String username, String password) { + expectedAuthorization = + "Basic " + Base64.getEncoder().encodeToString((username + ':' + password).getBytes(UTF_8)); + } + + @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, + FilterChain chain) throws ServletException, IOException { + String authHeader = req.getHeader("Authorization"); + if (expectedAuthorization.equals(authHeader)) { + chain.doFilter(req, res); // Pass on the supplied credentials + return; + } + res.setHeader("WWW-Authenticate", "Basic realm=\"Realm'\""); + res.sendError(HttpServletResponse.SC_UNAUTHORIZED); // Return 401 otherwise. + } } }