diff --git a/proxies/skjermingsregister-proxy/build.gradle b/proxies/skjermingsregister-proxy/build.gradle index a711ce5a38a..ec506c44b71 100644 --- a/proxies/skjermingsregister-proxy/build.gradle +++ b/proxies/skjermingsregister-proxy/build.gradle @@ -23,7 +23,7 @@ sonarqube { property "sonar.host.url", "https://sonarcloud.io" property "sonar.java.coveragePlugin", "jacoco" property "sonar.language", "java" - property "sonar.token", System.getenv("SONAR_TOKEN") + property "sonar.login", System.getenv("SONAR_TOKEN") property "sonar.organization", "navikt" property "sonar.project.monorepo.enabled", true property "sonar.projectKey", "testnav-skjermingsregister-proxy" @@ -50,25 +50,31 @@ repositories { } dependencies { - + implementation 'no.nav.testnav.libs:data-transfer-objects' implementation 'no.nav.testnav.libs:reactive-core' implementation 'no.nav.testnav.libs:reactive-proxy' + implementation 'no.nav.testnav.libs:reactive-security' + implementation 'no.nav.testnav.libs:security-core' implementation 'no.nav.testnav.libs:security-token-service' - implementation 'no.nav.testnav.libs:data-transfer-objects' implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap' // TODO remove legacy bootstrap config - implementation 'org.springframework.cloud:spring-cloud-starter-vault-config' implementation 'org.springframework.cloud:spring-cloud-starter-gateway' - + implementation 'org.springframework.cloud:spring-cloud-starter-vault-config' implementation 'net.logstash.logback:logstash-logback-encoder:7.4' implementation 'org.hibernate.validator:hibernate-validator' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock' + testImplementation 'org.springframework.security:spring-security-test' + + annotationProcessor 'org.projectlombok:lombok' + implementation 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' } + java { toolchain { languageVersion = JavaLanguageVersion.of(21) diff --git a/proxies/skjermingsregister-proxy/config.yml b/proxies/skjermingsregister-proxy/config.yml index 5b935a86541..615a070f4e1 100644 --- a/proxies/skjermingsregister-proxy/config.yml +++ b/proxies/skjermingsregister-proxy/config.yml @@ -1,14 +1,29 @@ -apiVersion: "nais.io/v1alpha1" -kind: "Application" +--- +apiVersion: nais.io/v1 +kind: AzureAdApplication +metadata: + name: testnav-skjermingsregister-proxy-trygdeetaten + namespace: dolly + labels: + team: dolly +spec: + secretName: azure-trygdeetaten-testnav-skjermingsregister-proxy-trygdeetaten + secretKeyPrefix: "AZURE_TRYGDEETATEN" + tenant: trygdeetaten.no +--- +apiVersion: nais.io/v1alpha1 +kind: Application metadata: name: testnav-skjermingsregister-proxy namespace: dolly labels: team: dolly spec: - image: "{{image}}" + image: {{image}} port: 8080 webproxy: true + tokenx: + enabled: true azure: application: allowAllUsers: true @@ -34,6 +49,10 @@ spec: - application: app-1 namespace: plattformsikkerhet cluster: dev-gcp + outbound: + rules: + - application: skjermede-personer + namespace: nom liveness: path: /internal/isAlive initialDelay: 4 @@ -55,5 +74,7 @@ spec: memory: 1024Mi limits: memory: 2048Mi + envFrom: + - secret: azure-trygdeetaten-testnav-skjermingsregister-proxy-trygdeetaten ingresses: - "https://testnav-skjermingsregister-proxy.dev-fss-pub.nais.io" \ No newline at end of file diff --git a/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/Consumers.java b/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/Consumers.java new file mode 100644 index 00000000000..c2f34180298 --- /dev/null +++ b/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/Consumers.java @@ -0,0 +1,22 @@ +package no.nav.testnav.proxies.skjermingsregisterproxy; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import no.nav.testnav.libs.securitycore.domain.ServerProperties; + +import static lombok.AccessLevel.PACKAGE; + +@Configuration +@ConfigurationProperties(prefix = "consumers") +@NoArgsConstructor(access = PACKAGE) +@Getter +@Setter(PACKAGE) +public class Consumers { + + private ServerProperties skjermingsregister; + +} diff --git a/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/LocalVaultConfig.java b/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/LocalVaultConfig.java new file mode 100644 index 00000000000..de96737cf66 --- /dev/null +++ b/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/LocalVaultConfig.java @@ -0,0 +1,42 @@ +package no.nav.testnav.proxies.skjermingsregisterproxy; + +import io.micrometer.common.lang.NonNullApi; +import no.nav.testnav.libs.reactiveproxy.config.DevConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.vault.annotation.VaultPropertySource; +import org.springframework.vault.authentication.ClientAuthentication; +import org.springframework.vault.authentication.TokenAuthentication; +import org.springframework.vault.client.VaultEndpoint; +import org.springframework.vault.config.AbstractVaultConfiguration; + +import static io.micrometer.common.util.StringUtils.isBlank; + +@Profile("local") +@Import(DevConfig.class) +@Configuration +@VaultPropertySource(value = "kv/preprod/fss/testnav-skjermingsregister-proxy/dev", ignoreSecretNotFound = false) +@NonNullApi +public class LocalVaultConfig extends AbstractVaultConfiguration { + + static final String TOKEN_PROPERTY_NAME = "spring.cloud.vault.token"; + + @Override + public VaultEndpoint vaultEndpoint() { + return VaultEndpoint.create("vault.adeo.no", 443); + } + + @Override + public ClientAuthentication clientAuthentication() { + if (System.getenv().containsKey("VAULT_TOKEN")) { + System.setProperty(TOKEN_PROPERTY_NAME, System.getenv("VAULT_TOKEN")); + } + var token = System.getProperty(TOKEN_PROPERTY_NAME); + if (isBlank(token)) { + throw new IllegalArgumentException("Påkrevet property '%s' er ikke satt.".formatted(TOKEN_PROPERTY_NAME)); + } + return new TokenAuthentication(System.getProperty(TOKEN_PROPERTY_NAME)); + } + +} \ No newline at end of file diff --git a/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/RouteLocatorConfig.java b/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/RouteLocatorConfig.java new file mode 100644 index 00000000000..8841a328d7c --- /dev/null +++ b/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/RouteLocatorConfig.java @@ -0,0 +1,48 @@ +package no.nav.testnav.proxies.skjermingsregisterproxy; + +import no.nav.testnav.libs.reactiveproxy.config.SecurityConfig; +import no.nav.testnav.libs.reactiveproxy.filter.AddAuthenticationRequestGatewayFilterFactory; +import no.nav.testnav.libs.reactivesecurity.config.SecureOAuth2ServerToServerConfiguration; +import no.nav.testnav.libs.reactivesecurity.exchange.azuread.TrygdeetatenAzureAdTokenService; +import no.nav.testnav.libs.securitycore.domain.AccessToken; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Import({ + SecureOAuth2ServerToServerConfiguration.class, + SecurityConfig.class +}) +@Configuration +public class RouteLocatorConfig { + + @Bean + public RouteLocator customRouteLocator( + RouteLocatorBuilder builder, + Consumers consumers, + GatewayFilter authenticationFilter + ) { + return builder + .routes() + .route(spec -> spec + .path("/**") + .filters(f -> f.filter(authenticationFilter)) + .uri(consumers.getSkjermingsregister().getUrl())) + .build(); + } + + @Bean + GatewayFilter getAuthenticationFilter( + TrygdeetatenAzureAdTokenService tokenService, + Consumers consumers + ) { + return AddAuthenticationRequestGatewayFilterFactory + .bearerAuthenticationHeaderFilter(() -> tokenService + .exchange(consumers.getSkjermingsregister()) + .map(AccessToken::getTokenValue)); + } + +} diff --git a/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/SkjermingsregisterProxyApplicationStarter.java b/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/SkjermingsregisterProxyApplicationStarter.java index 170d704c300..c4576ca8987 100644 --- a/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/SkjermingsregisterProxyApplicationStarter.java +++ b/proxies/skjermingsregister-proxy/src/main/java/no/nav/testnav/proxies/skjermingsregisterproxy/SkjermingsregisterProxyApplicationStarter.java @@ -1,51 +1,16 @@ package no.nav.testnav.proxies.skjermingsregisterproxy; import no.nav.testnav.libs.reactivecore.config.CoreConfig; -import no.nav.testnav.libs.reactiveproxy.config.DevConfig; -import no.nav.testnav.libs.reactiveproxy.config.SecurityConfig; -import no.nav.testnav.libs.reactiveproxy.filter.AddAuthenticationRequestGatewayFilterFactory; -import no.nav.testnav.libs.securitytokenservice.StsOidcTokenService; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -@Import({ - CoreConfig.class, - DevConfig.class, - SecurityConfig.class -}) @SpringBootApplication +@Import(CoreConfig.class) public class SkjermingsregisterProxyApplicationStarter { + public static void main(String[] args) { SpringApplication.run(SkjermingsregisterProxyApplicationStarter.class, args); } - @Bean - public StsOidcTokenService stsOidcTokenService( - @Value("${sts.token.provider.url}") String url, - @Value("${sts.token.provider.username}") String username, - @Value("${sts.token.provider.password}") String password) { - - return new StsOidcTokenService(url, username, password); - } - - @Bean - public RouteLocator customRouteLocator(RouteLocatorBuilder builder, StsOidcTokenService stsOidcTokenService) { - - var addAuthenticationHeaderFilter = AddAuthenticationRequestGatewayFilterFactory - .bearerAuthenticationHeaderFilter(stsOidcTokenService::getToken); - - return builder.routes() - .route(spec -> spec - .path("/**") - .filters(filterSpec -> filterSpec - .filter(addAuthenticationHeaderFilter)) - .uri("http://skjermede-personer.nom.svc.nais.local/")) - .build(); - } - -} +} \ No newline at end of file diff --git a/proxies/skjermingsregister-proxy/src/main/resources/application-dev.yml b/proxies/skjermingsregister-proxy/src/main/resources/application-dev.yml deleted file mode 100644 index 26224023aab..00000000000 --- a/proxies/skjermingsregister-proxy/src/main/resources/application-dev.yml +++ /dev/null @@ -1,2 +0,0 @@ -azure.app.client.id: ${client_id} -azure.app.client.secret: ${client_secret} diff --git a/proxies/skjermingsregister-proxy/src/main/resources/application.yml b/proxies/skjermingsregister-proxy/src/main/resources/application.yml index f9a72f66cf5..d34cb66d2b0 100644 --- a/proxies/skjermingsregister-proxy/src/main/resources/application.yml +++ b/proxies/skjermingsregister-proxy/src/main/resources/application.yml @@ -12,7 +12,7 @@ spring: jwk-set-uri: ${AAD_ISSUER_URI}/discovery/v2.0/keys accepted-audience: ${azure.app.client.id}, api://${azure.app.client.id} tokenx: - issuer-uri: https://tokenx.dev-gcp.nav.cloud.nais.io + issuer-uri: https://tokenx.dev-gcp.nav.cloud.nais.io jwk-set-uri: https://tokenx.dev-gcp.nav.cloud.nais.io/jwks accepted-audience: ${TOKEN_X_CLIENT_ID} cloud: @@ -20,9 +20,9 @@ spring: httpclient: response-timeout: 30s -sts: - token: - provider: - url: https://security-token-service.dev.adeo.no/rest/v1/sts/token - username: ${STS_TOKEN_PROVIDER_USERNAME} - password: ${STS_TOKEN_PROVIDER_PASSWORD} +consumers: + skjermingsregister: + name: skjermede-personer + namespace: nom + url: https://skjermede-personer.dev.adeo.no + cluster: dev-fss diff --git a/proxies/skjermingsregister-proxy/src/main/resources/logback-spring.xml b/proxies/skjermingsregister-proxy/src/main/resources/logback-spring.xml index 52c15597b3c..e8c92e3122b 100644 --- a/proxies/skjermingsregister-proxy/src/main/resources/logback-spring.xml +++ b/proxies/skjermingsregister-proxy/src/main/resources/logback-spring.xml @@ -22,7 +22,7 @@ - + @@ -37,4 +37,5 @@ + \ No newline at end of file diff --git a/proxies/skjermingsregister-proxy/src/test/java/no/nav/testnav/proxies/skjermingsregisterproxy/ApplicationContextTest.java b/proxies/skjermingsregister-proxy/src/test/java/no/nav/testnav/proxies/skjermingsregisterproxy/ApplicationContextTest.java deleted file mode 100644 index 9c83fa6454c..00000000000 --- a/proxies/skjermingsregister-proxy/src/test/java/no/nav/testnav/proxies/skjermingsregisterproxy/ApplicationContextTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package no.nav.testnav.proxies.skjermingsregisterproxy; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@ActiveProfiles("test") -class ApplicationContextTest { - - @MockBean - public ReactiveJwtDecoder reactiveJwtDecoder; - - @Test - @SuppressWarnings("java:S2699") - void load_app_context() { - } -} diff --git a/proxies/skjermingsregister-proxy/src/test/java/no/nav/testnav/proxies/skjermingsregisterproxy/LocalVaultConfigTest.java b/proxies/skjermingsregister-proxy/src/test/java/no/nav/testnav/proxies/skjermingsregisterproxy/LocalVaultConfigTest.java new file mode 100644 index 00000000000..2527e4fe89b --- /dev/null +++ b/proxies/skjermingsregister-proxy/src/test/java/no/nav/testnav/proxies/skjermingsregisterproxy/LocalVaultConfigTest.java @@ -0,0 +1,45 @@ +package no.nav.testnav.proxies.skjermingsregisterproxy; + +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.vault.authentication.TokenAuthentication; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +@ActiveProfiles("test") +class LocalVaultConfigTest { + + @Test + void missingSystemPropertyShouldFail() { + assertThatThrownBy(new LocalVaultConfig()::clientAuthentication) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void environmentPropertyShouldTakePrecedenceOverSystemProperty() { + try { + System.setProperty(LocalVaultConfig.TOKEN_PROPERTY_NAME, ""); + assertThat(new LocalVaultConfig()) + .isNotNull() + .satisfies( + config -> assertThat(config.clientAuthentication()) + .isNotNull() + .isInstanceOf(TokenAuthentication.class) + ); + } finally { + System.clearProperty(LocalVaultConfig.TOKEN_PROPERTY_NAME); + } + } + + @Test + void vaultEndpointShouldBeKnown() { + assertThat(new LocalVaultConfig().vaultEndpoint()) + .isNotNull() + .satisfies(endpoint -> { + assertThat(endpoint.getHost()).isEqualTo("vault.adeo.no"); + assertThat(endpoint.getPort()).isEqualTo(443); + }); + } + +} diff --git a/proxies/skjermingsregister-proxy/src/test/java/no/nav/testnav/proxies/skjermingsregisterproxy/RouteLocatorConfigTest.java b/proxies/skjermingsregister-proxy/src/test/java/no/nav/testnav/proxies/skjermingsregisterproxy/RouteLocatorConfigTest.java new file mode 100644 index 00000000000..c29d2faf621 --- /dev/null +++ b/proxies/skjermingsregister-proxy/src/test/java/no/nav/testnav/proxies/skjermingsregisterproxy/RouteLocatorConfigTest.java @@ -0,0 +1,68 @@ +package no.nav.testnav.proxies.skjermingsregisterproxy; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockOAuth2Login; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = "consumers.skjermingsregister.url=http://localhost:${wiremock.server.port}" +) +@AutoConfigureWireMock(port = 0) +@AutoConfigureWebTestClient +@ActiveProfiles("test") +class RouteLocatorConfigTest { + + @Autowired + private WebTestClient webClient; + + @Test + void shouldRouteToStub() { + + stubFor( + get(urlEqualTo("/testing/route")) + .willReturn( + aResponse() + .withStatus(HttpStatus.OK.value()) + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE) + .withBody("Some content") + ) + ); + + webClient + .mutateWith(mockOAuth2Login()) + .get().uri("/testing/route") + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.TEXT_PLAIN_VALUE) + .expectBody(String.class).isEqualTo("Some content"); + + } + + @TestConfiguration + static class TestAuthenticationConfig { + + @Primary + @Bean + GatewayFilter getNoopAuthenticationFilter() { + return (exchange, chain) -> chain.filter(exchange); + + } + + } + +}