From 20bb106e1600a6e1fa165e5c42b85ca866e122a6 Mon Sep 17 00:00:00 2001 From: Jens-Otto Larsen Date: Tue, 20 Aug 2024 20:04:51 +0200 Subject: [PATCH 1/2] =?UTF-8?q?Sanere=20utg=C3=A5ende=20STS,=20standardise?= =?UTF-8?q?re=20proxy,=20maskinporten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vedtak/klient/http/ProxyHttpClient.java | 20 +-- .../nav/vedtak/klient/http/ProxyProperty.java | 47 +++++++ .../oidc/config/MaskinportenProperty.java | 15 +++ .../oidc/config/impl/OidcProviderConfig.java | 11 +- .../impl/WellKnownConfigurationHelper.java | 29 ++--- .../oidc/jwks/JwksKeyHandlerImpl.java | 24 ++-- .../sikkerhet/oidc/token/TokenProvider.java | 34 +----- .../oidc/token/impl/GeneriskTokenKlient.java | 41 +++++-- .../impl/MaskinportenAssertionGenerator.java | 63 ++++++++++ .../token/impl/MaskinportenTokenKlient.java | 115 ++++++++++++++++++ .../oidc/token/impl/StsSystemTokenKlient.java | 61 ---------- .../token/impl}/TokenXAssertionGenerator.java | 38 +++--- .../oidc/token/impl/TokenXExchangeKlient.java | 36 +++--- .../vedtak/sikkerhet/tokenx/TokenXchange.java | 39 ------ .../TestMaskinportenAssertionGenerator.java | 47 +++++++ .../impl/TestTokenXAssertionGenerator.java} | 6 +- .../felles/integrasjon/rest/NavHeaders.java | 2 - .../integrasjon/rest/OidcContextSupplier.java | 5 +- .../felles/integrasjon/rest/RestRequest.java | 22 ++-- .../felles/integrasjon/rest/TokenFlow.java | 4 +- .../rest/TestRestClientConfig.java | 4 +- 21 files changed, 391 insertions(+), 272 deletions(-) create mode 100644 felles/klient/src/main/java/no/nav/vedtak/klient/http/ProxyProperty.java create mode 100644 felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/MaskinportenProperty.java create mode 100644 felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenAssertionGenerator.java create mode 100644 felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenTokenKlient.java delete mode 100644 felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/StsSystemTokenKlient.java rename felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/{tokenx => oidc/token/impl}/TokenXAssertionGenerator.java (64%) delete mode 100644 felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/tokenx/TokenXchange.java create mode 100644 felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TestMaskinportenAssertionGenerator.java rename felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/{tokenx/TestAssertionGenerator.java => oidc/token/impl/TestTokenXAssertionGenerator.java} (85%) diff --git a/felles/klient/src/main/java/no/nav/vedtak/klient/http/ProxyHttpClient.java b/felles/klient/src/main/java/no/nav/vedtak/klient/http/ProxyHttpClient.java index fdf5cf97a..5f28b4de2 100644 --- a/felles/klient/src/main/java/no/nav/vedtak/klient/http/ProxyHttpClient.java +++ b/felles/klient/src/main/java/no/nav/vedtak/klient/http/ProxyHttpClient.java @@ -1,30 +1,15 @@ package no.nav.vedtak.klient.http; -import no.nav.foreldrepenger.konfig.Environment; - -import java.net.InetSocketAddress; -import java.net.ProxySelector; -import java.net.URI; import java.net.http.HttpClient; import java.time.Duration; -import java.util.Optional; public final class ProxyHttpClient extends BaseHttpClient { - private static final Environment ENV = Environment.current(); - - private static final String AZURE_HTTP_PROXY = "azure.http.proxy"; - private static final String PROXY_KEY = "proxy.url"; - private static final String DEFAULT_PROXY_URL = "http://webproxy.nais:8088"; - private static ProxyHttpClient CLIENT; private ProxyHttpClient() { super(HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(15)) - .proxy(Optional.ofNullable(ENV.isFss() ? URI.create(ENV.getProperty(AZURE_HTTP_PROXY, getDefaultProxy())) : null) - .map(p -> new InetSocketAddress(p.getHost(), p.getPort())) - .map(ProxySelector::of) - .orElse(HttpClient.Builder.NO_PROXY)).build()); + .proxy(ProxyProperty.getProxySelectorIfFSS()).build()); } public static synchronized ProxyHttpClient client() { @@ -36,7 +21,4 @@ public static synchronized ProxyHttpClient client() { return inst; } - private static String getDefaultProxy() { - return ENV.getProperty(PROXY_KEY, DEFAULT_PROXY_URL); - } } diff --git a/felles/klient/src/main/java/no/nav/vedtak/klient/http/ProxyProperty.java b/felles/klient/src/main/java/no/nav/vedtak/klient/http/ProxyProperty.java new file mode 100644 index 000000000..ca3ff2bce --- /dev/null +++ b/felles/klient/src/main/java/no/nav/vedtak/klient/http/ProxyProperty.java @@ -0,0 +1,47 @@ +package no.nav.vedtak.klient.http; + +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.net.URI; +import java.net.http.HttpClient; +import java.util.Optional; + +import no.nav.foreldrepenger.konfig.Environment; + +/** + * Standard navn på environment injisert av NAIS når maskinporten er enabled + * Dvs naiserator:spec:maskinporten:enabled: true + */ +public class ProxyProperty { + private static final Environment ENV = Environment.current(); + + private static final String AZURE_HTTP_PROXY = "azure.http.proxy"; + private static final String DEFAULT_PROXY_URL = "http://webproxy.nais:8088"; + + private ProxyProperty() { + } + + public static URI getProxy() { + return URI.create(ENV.getProperty(AZURE_HTTP_PROXY, DEFAULT_PROXY_URL)); + } + + public static URI getProxyIfFSS() { + return ENV.isFss() ? getProxy() : null; + } + + public static ProxySelector getProxySelector() { + var proxy = getProxy(); + return ProxySelector.of(new InetSocketAddress(proxy.getHost(), proxy.getPort())); + } + + public static ProxySelector getProxySelectorIfFSS() { + return ENV.isFss() ? getProxySelector() : HttpClient.Builder.NO_PROXY; + } + + public static ProxySelector getProxySelector(URI proxy) { + return Optional.ofNullable(proxy) + .map(p -> new InetSocketAddress(p.getHost(), p.getPort())) + .map(ProxySelector::of) + .orElse(HttpClient.Builder.NO_PROXY); + } +} diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/MaskinportenProperty.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/MaskinportenProperty.java new file mode 100644 index 000000000..897fd55cb --- /dev/null +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/MaskinportenProperty.java @@ -0,0 +1,15 @@ +package no.nav.vedtak.sikkerhet.oidc.config; + +/** + * Standard navn på environment injisert av NAIS når maskinporten er enabled + * Dvs naiserator:spec:maskinporten:enabled: true + */ +public enum MaskinportenProperty { + MASKINPORTEN_CLIENT_ID, + MASKINPORTEN_CLIENT_JWK, + MASKINPORTEN_SCOPES, // Må angis i naiserator:spec:maskinporten:scopes:consumes: (-name: "") + MASKINPORTEN_WELL_KNOWN_URL, // Sanere bruk av well known - bruk heller NAIS/env + MASKINPORTEN_ISSUER, + MASKINPORTEN_TOKEN_ENDPOINT + +} diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/OidcProviderConfig.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/OidcProviderConfig.java index 463f94d17..77a72a10b 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/OidcProviderConfig.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/OidcProviderConfig.java @@ -18,6 +18,7 @@ import no.nav.foreldrepenger.konfig.Environment; import no.nav.vedtak.exception.TekniskException; +import no.nav.vedtak.klient.http.ProxyProperty; import no.nav.vedtak.sikkerhet.kontekst.Systembruker; import no.nav.vedtak.sikkerhet.oidc.config.AzureProperty; import no.nav.vedtak.sikkerhet.oidc.config.OpenIDConfiguration; @@ -29,10 +30,6 @@ public final class OidcProviderConfig { private static final Logger LOG = LoggerFactory.getLogger(OidcProviderConfig.class); private static final String STS_WELL_KNOWN_URL = "oidc.sts.well.known.url"; - private static final String AZURE_HTTP_PROXY = "azure.http.proxy"; // settes ikke av naiserator - private static final String PROXY_KEY = "proxy.url"; // FP-oppsett lite brukt - private static final String DEFAULT_PROXY_URL = "http://webproxy.nais:8088"; - private static Set providers = new HashSet<>(); private final Set instanceProviders; @@ -116,7 +113,7 @@ private static OpenIDConfiguration createStsConfiguration(String wellKnownUrl) { @SuppressWarnings("unused") private static OpenIDConfiguration createAzureAppConfiguration() { - var proxyUrl = (ENV.isFss() && ENV.isProd()) ? URI.create(ENV.getProperty(AZURE_HTTP_PROXY, getDefaultProxy())) : null; + var proxyUrl = (ENV.isFss() && ENV.isProd()) ? ProxyProperty.getProxy() : null; return createConfiguration(OpenIDProvider.AZUREAD, getAzureProperty(AzureProperty.AZURE_OPENID_CONFIG_ISSUER), getAzureProperty(AzureProperty.AZURE_OPENID_CONFIG_JWKS_URI), @@ -172,10 +169,6 @@ private static OpenIDConfiguration createConfiguration(OpenIDProvider type, skipAudienceValidation); } - private static String getDefaultProxy() { - return ENV.getProperty(PROXY_KEY, DEFAULT_PROXY_URL); - } - private static URI tilURI(String url, String key, OpenIDProvider provider) { try { return URI.create(url); diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/WellKnownConfigurationHelper.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/WellKnownConfigurationHelper.java index c78d657c7..10f85ae04 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/WellKnownConfigurationHelper.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/config/impl/WellKnownConfigurationHelper.java @@ -1,8 +1,6 @@ package no.nav.vedtak.sikkerhet.oidc.config.impl; import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -19,6 +17,7 @@ import com.fasterxml.jackson.databind.ObjectReader; import no.nav.vedtak.exception.TekniskException; +import no.nav.vedtak.klient.http.ProxyProperty; import no.nav.vedtak.mapper.json.DefaultJsonMapper; public class WellKnownConfigurationHelper { @@ -28,7 +27,7 @@ public class WellKnownConfigurationHelper { public static final String STANDARD_WELL_KNOWN_PATH = ".well-known/openid-configuration"; - private static Map wellKnownConfigMap = Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map wellKnownConfigMap = Collections.synchronizedMap(new LinkedHashMap<>()); public static WellKnownOpenIdConfiguration getWellKnownConfig(URI wellKnownUrl) { return getWellKnownConfig(wellKnownUrl.toString(), null); @@ -66,19 +65,17 @@ static Optional getTokenEndpointFra(String wellKnownURL, URI proxyUrl) { } private static WellKnownOpenIdConfiguration hentWellKnownConfig(String wellKnownURL, URI proxy) { - try { - if (wellKnownURL == null) { - return null; - } - if (!wellKnownURL.toLowerCase().contains(STANDARD_WELL_KNOWN_PATH)) { - // TODO: øk til warn eller prøv å legge på / standard path med - LOG.info("WELLKNOWN OPENID-CONFIGURATION url uten standard suffix {}", wellKnownURL); - } - var useProxySelector = Optional.ofNullable(proxy) - .map(p -> new InetSocketAddress(p.getHost(), p.getPort())) - .map(ProxySelector::of) - .orElse(HttpClient.Builder.NO_PROXY); - var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).proxy(useProxySelector).build(); + if (wellKnownURL == null) { + return null; + } + if (!wellKnownURL.toLowerCase().contains(STANDARD_WELL_KNOWN_PATH)) { + // TODO: øk til warn eller prøv å legge på / standard path med + LOG.info("WELLKNOWN OPENID-CONFIGURATION url uten standard suffix {}", wellKnownURL); + } + try (var client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(10)) + .proxy(ProxyProperty.getProxySelector(proxy)) + .build()) { var request = HttpRequest.newBuilder().uri(URI.create(wellKnownURL)).header("accept", "application/json").GET().build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()).body(); return response != null ? READER.readValue(response) : null; diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/jwks/JwksKeyHandlerImpl.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/jwks/JwksKeyHandlerImpl.java index 32da41305..ee0b24941 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/jwks/JwksKeyHandlerImpl.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/jwks/JwksKeyHandlerImpl.java @@ -3,8 +3,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -22,6 +20,7 @@ import org.slf4j.LoggerFactory; import no.nav.vedtak.exception.TekniskException; +import no.nav.vedtak.klient.http.ProxyProperty; public class JwksKeyHandlerImpl implements JwksKeyHandler { @@ -88,19 +87,14 @@ private static String nativeGet(URI url, boolean useProxyForJwks, URI proxy) { if (url == null) { throw new TekniskException("F-836283", "Mangler konfigurasjon av jwks url"); } - try { - if (useProxyForJwks && proxy == null) { - throw kunneIkkeOppdatereJwksCache(url, new IllegalArgumentException("Skal bruke proxy, men ingen verdi angitt")); - } - var useProxySelector = Optional.ofNullable(proxy) - .map(p -> new InetSocketAddress(p.getHost(), p.getPort())) - .map(ProxySelector::of) - .orElse(HttpClient.Builder.NO_PROXY); - var client = HttpClient.newBuilder() - .followRedirects(HttpClient.Redirect.NEVER) - .connectTimeout(Duration.ofSeconds(20)) - .proxy(useProxySelector) - .build(); + if (useProxyForJwks && proxy == null) { + throw kunneIkkeOppdatereJwksCache(url, new IllegalArgumentException("Skal bruke proxy, men ingen verdi angitt")); + } + try (var client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NEVER) + .connectTimeout(Duration.ofSeconds(20)) + .proxy(ProxyProperty.getProxySelector(proxy)) + .build()) { var request = HttpRequest.newBuilder().header("Accept", "application/json").timeout(Duration.ofSeconds(10)).uri(url).GET().build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString(UTF_8)); diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/TokenProvider.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/TokenProvider.java index b2af2d0dd..dba4fc449 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/TokenProvider.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/TokenProvider.java @@ -12,9 +12,7 @@ import no.nav.vedtak.sikkerhet.oidc.config.OpenIDProvider; import no.nav.vedtak.sikkerhet.oidc.token.impl.AzureBrukerTokenKlient; import no.nav.vedtak.sikkerhet.oidc.token.impl.AzureSystemTokenKlient; -import no.nav.vedtak.sikkerhet.oidc.token.impl.StsSystemTokenKlient; import no.nav.vedtak.sikkerhet.oidc.token.impl.TokenXExchangeKlient; -import no.nav.vedtak.sikkerhet.tokenx.TokenXchange; public final class TokenProvider { @@ -52,30 +50,13 @@ private static OpenIDToken getOutgoingTokenFor(RequestKontekst requestKontekst, var identType = Optional.ofNullable(requestKontekst.getIdentType()).orElse(IdentType.InternBruker); return switch (providerIncoming) { case AZUREAD -> identType.erSystem() ? getAzureSystemToken(scopes) : veksleAzureAccessToken(requestKontekst.getUid(), incoming, scopes); - case TOKENX -> TokenXchange.exchange(incoming, scopes); + case TOKENX -> tokenXchange(incoming, scopes); case STS -> getAzureSystemToken(scopes); }; } - public static OpenIDToken getTokenForSystem() { - return getTokenForSystem(OpenIDProvider.STS, null); - } - - public static OpenIDToken getTokenXFraKontekst() { - var kontekst = KONTEKST_PROVIDER.getKontekst(); - if (kontekst instanceof RequestKontekst requestKontekst) { - return OpenIDProvider.TOKENX.equals(getProvider(requestKontekst.getToken())) ? requestKontekst.getToken() : null; - } else { - throw new IllegalStateException("Mangler SikkerhetContext - skal ikke provide token"); - } - } - - public static OpenIDToken getTokenForSystem(OpenIDProvider provider, String scopes) { - return switch (provider) { - case AZUREAD -> getAzureSystemToken(scopes); - case STS -> getStsSystemToken(); - case TOKENX -> throw new IllegalStateException("Ikke bruk TokenX til kall i systemkontekst"); - }; + public static OpenIDToken getTokenForSystem(String scopes) { + return getAzureSystemToken(scopes); } // Endre til AzureClientId ved overgang til system = azure @@ -91,10 +72,6 @@ public static String getCurrentConsumerId() { return Optional.ofNullable(kontekst.getKonsumentId()).orElseGet(kontekst::getUid); } - private static OpenIDToken getStsSystemToken() { - return StsSystemTokenKlient.hentAccessToken(); - } - private static OpenIDToken getAzureSystemToken(String scopes) { return AzureSystemTokenKlient.instance().hentAccessToken(scopes); } @@ -103,9 +80,8 @@ private static OpenIDToken veksleAzureAccessToken(String uid, OpenIDToken incomi return AzureBrukerTokenKlient.instance().oboExchangeToken(uid, incoming, scopes); } - public static OpenIDToken exchangeTokenX(OpenIDToken token, String assertion, String scopes) { - // Assertion må være generert av den som skal bytte. Et JWT, RSA-signert, basert på injisert private jwk - return TokenXExchangeKlient.instance().exchangeToken(token, assertion, scopes); + public static OpenIDToken tokenXchange(OpenIDToken token, String scopes) { + return TokenXExchangeKlient.instance().exchangeToken(token, scopes); } private static OpenIDProvider getProvider(OpenIDToken token) { diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/GeneriskTokenKlient.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/GeneriskTokenKlient.java index c94699f08..3a1d0dd3c 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/GeneriskTokenKlient.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/GeneriskTokenKlient.java @@ -3,37 +3,43 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ProxySelector; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; -import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectReader; import no.nav.vedtak.exception.TekniskException; +import no.nav.vedtak.klient.http.ProxyProperty; import no.nav.vedtak.mapper.json.DefaultJsonMapper; public class GeneriskTokenKlient { + private static final Logger LOG = LoggerFactory.getLogger(GeneriskTokenKlient.class); + private static final ObjectReader READER = DefaultJsonMapper.getObjectMapper().readerFor(OidcTokenResponse.class); + public static OidcTokenResponse hentTokenRetryable(HttpRequest request, URI proxy, int retries) { + int i = retries; + while (i-- > 0) { + try { + return hentToken(request, proxy); + } catch (TekniskException e) { + LOG.info("Feilet {}. gang ved henting av token. Prøver på nytt", retries - i, e); + } + } + return hentToken(request, proxy); + } + public static OidcTokenResponse hentToken(HttpRequest request, URI proxy) { - try { - var useProxySelector = Optional.ofNullable(proxy) - .map(p -> new InetSocketAddress(p.getHost(), p.getPort())) - .map(ProxySelector::of) - .orElse(HttpClient.Builder.NO_PROXY); - var client = HttpClient.newBuilder() - .followRedirects(HttpClient.Redirect.NEVER) - .connectTimeout(Duration.ofSeconds(20)) - .proxy(useProxySelector) - .build(); + try (var client = hentEllerByggHttpClient(proxy)) { // På sikt vurder å bruke en generell klient eller å cache. De er blitt autocloseable var response = client.send(request, HttpResponse.BodyHandlers.ofString(UTF_8)); if (response == null || response.body() == null) { throw new TekniskException("F-157385", "Kunne ikke hente token"); @@ -48,4 +54,13 @@ public static OidcTokenResponse hentToken(HttpRequest request, URI proxy) { throw new TekniskException("F-432938", "InterruptedException ved henting av token", e); } } + + private static HttpClient hentEllerByggHttpClient(URI proxy) { + return HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NEVER) + .connectTimeout(Duration.ofSeconds(15)) + .proxy(ProxyProperty.getProxySelector(proxy)) + .build(); + } + } diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenAssertionGenerator.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenAssertionGenerator.java new file mode 100644 index 000000000..afcbb9cb0 --- /dev/null +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenAssertionGenerator.java @@ -0,0 +1,63 @@ +package no.nav.vedtak.sikkerhet.oidc.token.impl; + +import java.util.Optional; + +import org.jose4j.json.JsonUtil; +import org.jose4j.jwk.RsaJsonWebKey; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.NumericDate; +import org.jose4j.lang.JoseException; + +final class MaskinportenAssertionGenerator { + + private final String clientId; + private final RsaJsonWebKey privateKey; + private final String issuer; + + + MaskinportenAssertionGenerator(String clientId, String issuer, String privateKey) { + this(clientId, issuer, rsaKey(privateKey)); + } + + MaskinportenAssertionGenerator(String clientId, String issuer, RsaJsonWebKey privateKey) { + this.issuer = issuer; + this.clientId = clientId; + this.privateKey = privateKey; + } + + String assertion(String scope, String resource) { + try { + var expirationTime = NumericDate.now(); + expirationTime.addSeconds(90); + JwtClaims claims = new JwtClaims(); + claims.setIssuer(clientId); + claims.setAudience(issuer); + claims.setIssuedAtToNow(); + claims.setNotBeforeMinutesInThePast(2); + claims.setExpirationTime(expirationTime); // Max 120s - men skal bare leve til man har fått token + claims.setGeneratedJwtId(); + claims.setClaim("scope", scope); + Optional.ofNullable(resource).ifPresent(r -> claims.setClaim("resource", r)); + + JsonWebSignature jws = new JsonWebSignature(); + jws.setPayload(claims.toJson()); + jws.setKey(privateKey.getPrivateKey()); + jws.setKeyIdHeaderValue(privateKey.getKeyId()); + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); + return jws.getCompactSerialization(); + } catch (JoseException e) { + throw new IllegalArgumentException(e); + } + } + + private static RsaJsonWebKey rsaKey(String privateJwk) { + try { + var map = JsonUtil.parseJson(privateJwk); + return new RsaJsonWebKey(map); + } catch (JoseException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenTokenKlient.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenTokenKlient.java new file mode 100644 index 000000000..a4dc2749c --- /dev/null +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenTokenKlient.java @@ -0,0 +1,115 @@ +package no.nav.vedtak.sikkerhet.oidc.token.impl; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static no.nav.vedtak.sikkerhet.oidc.token.impl.Headers.APPLICATION_FORM_ENCODED; +import static no.nav.vedtak.sikkerhet.oidc.token.impl.Headers.CONTENT_TYPE; + +import java.net.URI; +import java.net.http.HttpRequest; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import no.nav.foreldrepenger.konfig.Environment; +import no.nav.vedtak.klient.http.ProxyProperty; +import no.nav.vedtak.sikkerhet.oidc.config.MaskinportenProperty; +import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; +import no.nav.vedtak.sikkerhet.oidc.token.TokenString; +import no.nav.vedtak.util.LRUCache; + +public final class MaskinportenTokenKlient { + + private static final Environment ENV = Environment.current(); + private static final Logger LOG = LoggerFactory.getLogger(MaskinportenTokenKlient.class); + + private static final int RETRIES = 1; // 1 attempt, then n retries + + private static MaskinportenTokenKlient INSTANCE; + + private final LRUCache obocache; + + private final URI tokenEndpoint; + private final List scopes; + private final URI proxyUrl; + private final MaskinportenAssertionGenerator assertionGenerator; + + + private MaskinportenTokenKlient() { + var clientId = getMaskinportenProperty(MaskinportenProperty.MASKINPORTEN_CLIENT_ID); + var privateKey = getMaskinportenProperty(MaskinportenProperty.MASKINPORTEN_CLIENT_JWK); + var issuer = getMaskinportenProperty(MaskinportenProperty.MASKINPORTEN_ISSUER); + this.assertionGenerator = new MaskinportenAssertionGenerator(clientId, issuer, privateKey); + this.tokenEndpoint = URI.create(getMaskinportenProperty(MaskinportenProperty.MASKINPORTEN_TOKEN_ENDPOINT)); + this.scopes = Arrays.stream(getMaskinportenProperty(MaskinportenProperty.MASKINPORTEN_SCOPES) + .split("\\s+")).toList(); + this.obocache = new LRUCache<>(200, TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)); + this.proxyUrl = ProxyProperty.getProxyIfFSS(); + } + + public static synchronized MaskinportenTokenKlient instance() { + var inst = INSTANCE; + if (inst == null) { + inst = new MaskinportenTokenKlient(); + INSTANCE = inst; + } + return inst; + } + + public OpenIDToken hentMaskinportenToken(String scope, String resource) { + if (!scopes.contains(scope)) { + throw new IllegalStateException("Scope " + scope + " not configured in nais"); + } + var cacheKey = cacheKey(scope, resource); + var tokenFromCache = getCachedToken(cacheKey); + if (tokenFromCache != null && tokenFromCache.isNotExpired()) { + return tokenFromCache.copy(); + } + + var response = hentToken(assertionGenerator.assertion(scope, resource)); + LOG.debug("Maskinporten fikk token av type {} utløper {}", response.token_type(), response.expires_in()); + + var newToken = new OpenIDToken(null, response.token_type(), new TokenString(response.access_token()), scope, response.expires_in()); + putTokenToCache(cacheKey, newToken); + return newToken.copy(); + } + + private OidcTokenResponse hentToken(String assertion) { + var request = HttpRequest.newBuilder() + .header("Cache-Control", "no-cache") + .header(CONTENT_TYPE, APPLICATION_FORM_ENCODED) + .timeout(Duration.ofSeconds(10)) + .uri(tokenEndpoint) + .POST(ofFormData(assertion)) + .build(); + return GeneriskTokenKlient.hentTokenRetryable(request, proxyUrl, RETRIES); + } + + private static HttpRequest.BodyPublisher ofFormData(String assertion) { + var formdata = "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&" + + "assertion=" + assertion; + return HttpRequest.BodyPublishers.ofString(formdata, UTF_8); + } + + private OpenIDToken getCachedToken(String key) { + return obocache.get(key); + } + + private void putTokenToCache(String key, OpenIDToken exchangedToken) { + obocache.put(key, exchangedToken); + } + + private String cacheKey(String scope, String resource) { + return scope + ":::" + Optional.ofNullable(resource).orElse(""); + } + + private static String getMaskinportenProperty(MaskinportenProperty property) { + return Optional.ofNullable(ENV.getProperty(property.name())) + .orElseGet(() -> ENV.getProperty(property.name().toLowerCase().replace('_', '.'))); + } + +} diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/StsSystemTokenKlient.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/StsSystemTokenKlient.java deleted file mode 100644 index 48a9f9d20..000000000 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/StsSystemTokenKlient.java +++ /dev/null @@ -1,61 +0,0 @@ -package no.nav.vedtak.sikkerhet.oidc.token.impl; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.net.http.HttpRequest; -import java.time.Duration; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import no.nav.vedtak.log.mdc.MDCOperations; -import no.nav.vedtak.sikkerhet.oidc.config.ConfigProvider; -import no.nav.vedtak.sikkerhet.oidc.config.OpenIDConfiguration; -import no.nav.vedtak.sikkerhet.oidc.config.OpenIDProvider; -import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; -import no.nav.vedtak.sikkerhet.oidc.token.TokenString; - -public class StsSystemTokenKlient { - - private static final Logger LOG = LoggerFactory.getLogger(StsSystemTokenKlient.class); - - private static final String SCOPE = "openid"; - - private static final OpenIDConfiguration OIDCONFIG = ConfigProvider.getOpenIDConfiguration(OpenIDProvider.STS).orElseThrow(); - - private static OpenIDToken accessToken; - - private StsSystemTokenKlient() { - } - - public static synchronized OpenIDToken hentAccessToken() { - if (accessToken != null && accessToken.isNotExpired()) { - return accessToken.copy(); - } - var response = hentToken(); - LOG.info("STS hentet og fikk token av type {} utløper {}", response.token_type(), response.expires_in()); - accessToken = new OpenIDToken(OpenIDProvider.STS, response.token_type(), new TokenString(response.access_token()), SCOPE, - response.expires_in()); - return accessToken.copy(); - } - - private static OidcTokenResponse hentToken() { - var request = HttpRequest.newBuilder() - .header(Headers.AUTHORIZATION, Headers.basicCredentials(OIDCONFIG.clientId(), OIDCONFIG.clientSecret())) - .header("Nav-Consumer-Id", OIDCONFIG.clientId()) - .header("Nav-Call-Id", MDCOperations.getCallId()) - .header("Cache-Control", "no-cache") - .header(Headers.CONTENT_TYPE, Headers.APPLICATION_FORM_ENCODED) - .timeout(Duration.ofSeconds(10)) - .uri(OIDCONFIG.tokenEndpoint()) - .POST(ofFormData()) - .build(); - return GeneriskTokenKlient.hentToken(request, null); - } - - private static HttpRequest.BodyPublisher ofFormData() { - var formdata = "grant_type=client_credentials&scope=" + SCOPE; - return HttpRequest.BodyPublishers.ofString(formdata, UTF_8); - } - -} diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/tokenx/TokenXAssertionGenerator.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXAssertionGenerator.java similarity index 64% rename from felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/tokenx/TokenXAssertionGenerator.java rename to felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXAssertionGenerator.java index 476c742a8..001824cdc 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/tokenx/TokenXAssertionGenerator.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXAssertionGenerator.java @@ -1,4 +1,4 @@ -package no.nav.vedtak.sikkerhet.tokenx; +package no.nav.vedtak.sikkerhet.oidc.token.impl; import java.net.URI; import java.util.Optional; @@ -8,29 +8,26 @@ import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.NumericDate; import org.jose4j.lang.JoseException; import no.nav.foreldrepenger.konfig.Environment; -import no.nav.vedtak.sikkerhet.oidc.config.ConfigProvider; import no.nav.vedtak.sikkerhet.oidc.config.OpenIDConfiguration; -import no.nav.vedtak.sikkerhet.oidc.config.OpenIDProvider; import no.nav.vedtak.sikkerhet.oidc.config.TokenXProperty; final class TokenXAssertionGenerator { - private static TokenXAssertionGenerator INSTANCE; + private static final Environment ENV = Environment.current(); private final String clientId; private final URI tokenEndpoint; private final RsaJsonWebKey privateKey; - private TokenXAssertionGenerator() { - this(ConfigProvider.getOpenIDConfiguration(OpenIDProvider.TOKENX).map(OpenIDConfiguration::tokenEndpoint).orElse(null), - ConfigProvider.getOpenIDConfiguration(OpenIDProvider.TOKENX).map(OpenIDConfiguration::clientId).orElse(null), - Optional.ofNullable(Environment.current().getProperty(TokenXProperty.TOKEN_X_PRIVATE_JWK.name())) - .or(() -> Optional.ofNullable( - Environment.current().getProperty(TokenXProperty.TOKEN_X_PRIVATE_JWK.name().toLowerCase().replace('_', '.')))) + TokenXAssertionGenerator(OpenIDConfiguration config) { + this(Optional.ofNullable(config).map(OpenIDConfiguration::tokenEndpoint).orElse(null), + Optional.ofNullable(config).map(OpenIDConfiguration::clientId).orElse(null), + Optional.ofNullable(getTokenXProperty(TokenXProperty.TOKEN_X_PRIVATE_JWK)) .map(TokenXAssertionGenerator::rsaKey) .orElse(null)); } @@ -42,24 +39,18 @@ private TokenXAssertionGenerator() { this.privateKey = privateKey; } - public static synchronized TokenXAssertionGenerator instance() { - var inst = INSTANCE; - if (inst == null) { - inst = new TokenXAssertionGenerator(); - INSTANCE = inst; - } - return inst; - } - - public String assertion() { + String assertion() { try { + var expirationTime = NumericDate.now(); + expirationTime.addSeconds(90); + JwtClaims claims = new JwtClaims(); claims.setSubject(clientId); claims.setIssuer(clientId); claims.setAudience(tokenEndpoint.toString()); claims.setIssuedAtToNow(); claims.setNotBeforeMinutesInThePast(2); - claims.setExpirationTimeMinutesInTheFuture(1); + claims.setExpirationTime(expirationTime); // Max 120s - men skal bare leve til man har fått token claims.setGeneratedJwtId(); JsonWebSignature jws = new JsonWebSignature(); @@ -81,4 +72,9 @@ private static RsaJsonWebKey rsaKey(String privateJwk) { throw new IllegalArgumentException(e); } } + + private static String getTokenXProperty(TokenXProperty property) { + return Optional.ofNullable(ENV.getProperty(property.name())) + .orElseGet(() -> ENV.getProperty(property.name().toLowerCase().replace('_', '.'))); + } } diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java index bad09cb9b..e1a048849 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java @@ -7,19 +7,17 @@ import java.time.Duration; import java.util.concurrent.TimeUnit; -import no.nav.vedtak.sikkerhet.kontekst.DefaultRequestKontekstProvider; -import no.nav.vedtak.sikkerhet.kontekst.KontekstProvider; -import no.nav.vedtak.util.LRUCache; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import no.nav.vedtak.exception.TekniskException; +import no.nav.vedtak.sikkerhet.kontekst.DefaultRequestKontekstProvider; +import no.nav.vedtak.sikkerhet.kontekst.KontekstProvider; import no.nav.vedtak.sikkerhet.oidc.config.ConfigProvider; import no.nav.vedtak.sikkerhet.oidc.config.OpenIDConfiguration; import no.nav.vedtak.sikkerhet.oidc.config.OpenIDProvider; import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; import no.nav.vedtak.sikkerhet.oidc.token.TokenString; +import no.nav.vedtak.util.LRUCache; public final class TokenXExchangeKlient { @@ -29,15 +27,18 @@ public final class TokenXExchangeKlient { private static TokenXExchangeKlient INSTANCE; - private LRUCache obocache; + private final LRUCache obocache; private final URI tokenEndpoint; + private final TokenXAssertionGenerator assertionGenerator; + private TokenXExchangeKlient() { var provider = ConfigProvider.getOpenIDConfiguration(OpenIDProvider.TOKENX); + this.assertionGenerator = new TokenXAssertionGenerator(provider.orElse(null)); this.tokenEndpoint = provider.map(OpenIDConfiguration::tokenEndpoint).orElse(null); - this.obocache = new LRUCache<>(2500, TimeUnit.MILLISECONDS.convert(90, TimeUnit.SECONDS)); + this.obocache = new LRUCache<>(2500, TimeUnit.MILLISECONDS.convert(110, TimeUnit.SECONDS)); } public static synchronized TokenXExchangeKlient instance() { @@ -49,7 +50,7 @@ public static synchronized TokenXExchangeKlient instance() { return inst; } - public OpenIDToken exchangeToken(OpenIDToken token, String assertion, String scopes) { + public OpenIDToken exchangeToken(OpenIDToken token, String scopes) { var audience = audience(scopes); var uid = KONTEKST_PROVIDER.getKontekst().getUid(); var tokenFromCache = getCachedToken(uid, audience); @@ -57,6 +58,9 @@ public OpenIDToken exchangeToken(OpenIDToken token, String assertion, String sco return tokenFromCache.copy(); } + // Assertion må være generert av den som skal bytte. Et JWT, RSA-signert, basert på injisert private jwk + var assertion = assertionGenerator.assertion(); + var response = hentToken(token, assertion, audience); LOG.debug("TokenX byttet og fikk token av type {} utløper {}", response.token_type(), response.expires_in()); @@ -74,22 +78,9 @@ private OidcTokenResponse hentToken(OpenIDToken token, String assertion, String .uri(tokenEndpoint) .POST(ofFormData(token, assertion, audience)) .build(); - return hentTokenRetryable(request); + return GeneriskTokenKlient.hentTokenRetryable(request, null, RETRIES); } - public static OidcTokenResponse hentTokenRetryable(HttpRequest request) { - int i = RETRIES; - while (i-- > 0) { - try { - return GeneriskTokenKlient.hentToken(request, null); - } catch (TekniskException e) { - LOG.info("Feilet {}. gang ved henting av token. Prøver på nytt", RETRIES - i, e); - } - } - return GeneriskTokenKlient.hentToken(request, null); - } - - private static HttpRequest.BodyPublisher ofFormData(OpenIDToken token, String assertion, String audience) { var formdata = "grant_type=urn:ietf:params:oauth:grant-type:token-exchange&" + "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&" @@ -118,4 +109,5 @@ private void putTokenToCache(String uid, String audience, OpenIDToken exchangedT private String cacheKey(String uid, String audience) { return uid + ":::" + audience; } + } diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/tokenx/TokenXchange.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/tokenx/TokenXchange.java deleted file mode 100644 index 1f723aeb8..000000000 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/tokenx/TokenXchange.java +++ /dev/null @@ -1,39 +0,0 @@ -package no.nav.vedtak.sikkerhet.tokenx; - - -import static no.nav.vedtak.log.util.ConfidentialMarkerFilter.CONFIDENTIAL; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import no.nav.foreldrepenger.konfig.Environment; -import no.nav.vedtak.sikkerhet.oidc.config.OpenIDProvider; -import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; -import no.nav.vedtak.sikkerhet.oidc.token.TokenProvider; - -/** - * Dette filteret skal brukes når man vet man mottar et token som støtter - * on-behalf-of, og når mottakende system krever kun dette - */ -public final class TokenXchange { - private static final Environment ENV = Environment.current(); - private static final Logger LOG = LoggerFactory.getLogger(TokenXchange.class); - - private TokenXchange() { - } - - public static OpenIDToken exchange(OpenIDToken token, String scopes) { - if (token != null && OpenIDProvider.TOKENX.equals(token.provider())) { - LOG.trace(CONFIDENTIAL, "Veksler tokenX token {} for {}", token, scopes); - var assertion = TokenXAssertionGenerator.instance().assertion(); - return TokenProvider.exchangeTokenX(token, assertion, scopes); - } else { - if (token != null && ENV.isLocal()) { - LOG.warn("Dette er intet tokenX token, sender originalt token videre til {} siden VTP er mangelfull her", scopes); - return token; - } else { - throw new IllegalStateException("Dette er intet tokenX token"); - } - } - } -} diff --git a/felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TestMaskinportenAssertionGenerator.java b/felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TestMaskinportenAssertionGenerator.java new file mode 100644 index 000000000..30795661d --- /dev/null +++ b/felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TestMaskinportenAssertionGenerator.java @@ -0,0 +1,47 @@ +package no.nav.vedtak.sikkerhet.oidc.token.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; + +import org.jose4j.jwt.MalformedClaimException; +import org.junit.jupiter.api.Test; + +import no.nav.vedtak.sikkerhet.oidc.validator.JwtUtil; +import no.nav.vedtak.sikkerhet.oidc.validator.KeyStoreTool; + +public class TestMaskinportenAssertionGenerator { + + @Test + void lag_signer_valider_maskinporten_assertion_med_ressurs() throws MalformedClaimException { + var generator = new MaskinportenAssertionGenerator("minKlient", "https://test.maskinporten.no/", KeyStoreTool.getJsonWebKey()); + var token = generator.assertion("mitt:scope:delvis", "https://beskyttet-ressurs.no/sti"); + + var claims = JwtUtil.getClaims(token); + assertThat(claims.getClaimValueAsString("iss")).isEqualTo("minKlient"); + assertThat(claims.getClaimValueAsString("aud")).contains("https://test.maskinporten.no/"); + assertThat(claims.getClaimValueAsString("scope")).contains("mitt:scope:delvis"); + assertThat(claims.getClaimValueAsString("resource")).contains("https://beskyttet-ressurs.no/sti"); + assertThat(Instant.ofEpochSecond(claims.getClaimValue("nbf", Long.class))).isBefore(Instant.now()); + assertThat(Instant.ofEpochSecond(claims.getClaimValue("iat", Long.class))).isBefore(Instant.now()); + assertThat(Instant.ofEpochSecond(claims.getClaimValue("exp", Long.class))).isAfter(Instant.now()); + + } + + @Test + void lag_signer_valider_maskinporten_assertion_uten_ressurs() throws MalformedClaimException { + var generator = new MaskinportenAssertionGenerator("minKlient", "https://test.maskinporten.no/", KeyStoreTool.getJsonWebKey()); + var token = generator.assertion("mitt:scope:delvis", null); + + var claims = JwtUtil.getClaims(token); + assertThat(claims.getClaimValueAsString("iss")).isEqualTo("minKlient"); + assertThat(claims.getClaimValueAsString("aud")).contains("https://test.maskinporten.no/"); + assertThat(claims.getClaimValueAsString("scope")).contains("mitt:scope:delvis"); + assertThat(claims.getClaimValueAsString("resource")).isNull(); + assertThat(Instant.ofEpochSecond(claims.getClaimValue("nbf", Long.class))).isBefore(Instant.now()); + assertThat(Instant.ofEpochSecond(claims.getClaimValue("iat", Long.class))).isBefore(Instant.now()); + assertThat(Instant.ofEpochSecond(claims.getClaimValue("exp", Long.class))).isAfter(Instant.now()); + + } + +} diff --git a/felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/tokenx/TestAssertionGenerator.java b/felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TestTokenXAssertionGenerator.java similarity index 85% rename from felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/tokenx/TestAssertionGenerator.java rename to felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TestTokenXAssertionGenerator.java index 034f5dbdc..f264c431b 100644 --- a/felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/tokenx/TestAssertionGenerator.java +++ b/felles/oidc/src/test/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TestTokenXAssertionGenerator.java @@ -1,4 +1,4 @@ -package no.nav.vedtak.sikkerhet.tokenx; +package no.nav.vedtak.sikkerhet.oidc.token.impl; import static org.assertj.core.api.Assertions.assertThat; @@ -11,7 +11,7 @@ import no.nav.vedtak.sikkerhet.oidc.validator.JwtUtil; import no.nav.vedtak.sikkerhet.oidc.validator.KeyStoreTool; -public class TestAssertionGenerator { +public class TestTokenXAssertionGenerator { @Test void lag_signer_valider_tokenx_assertion() throws MalformedClaimException { @@ -21,7 +21,7 @@ void lag_signer_valider_tokenx_assertion() throws MalformedClaimException { var claims = JwtUtil.getClaims(token); assertThat(claims.getClaimValueAsString("iss")).isEqualTo("minKlient"); assertThat(claims.getClaimValueAsString("sub")).isEqualTo("minKlient"); - assertThat(claims.getClaimValueAsString("aud").toString()).contains("https://token/endpoint"); + assertThat(claims.getClaimValueAsString("aud")).contains("https://token/endpoint"); assertThat(Instant.ofEpochSecond(claims.getClaimValue("nbf", Long.class))).isBefore(Instant.now()); assertThat(Instant.ofEpochSecond(claims.getClaimValue("iat", Long.class))).isBefore(Instant.now()); assertThat(Instant.ofEpochSecond(claims.getClaimValue("exp", Long.class))).isAfter(Instant.now()); diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/NavHeaders.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/NavHeaders.java index a5d63dd09..ac398b61d 100644 --- a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/NavHeaders.java +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/NavHeaders.java @@ -20,7 +20,5 @@ private NavHeaders() { public static final String HEADER_NAV_CALLID = "Nav-Callid"; public static final String HEADER_NAV_LOWER_CALL_ID = "nav-call-id"; public static final String HEADER_NAV_CONSUMER_ID = "Nav-Consumer-Id"; - public static final String HEADER_NAV_CONSUMER_TOKEN = "Nav-Consumer-Token"; - } diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/OidcContextSupplier.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/OidcContextSupplier.java index 828235400..07f907a7c 100644 --- a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/OidcContextSupplier.java +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/OidcContextSupplier.java @@ -3,7 +3,6 @@ import java.util.function.Supplier; import no.nav.vedtak.sikkerhet.kontekst.SikkerhetContext; -import no.nav.vedtak.sikkerhet.oidc.config.OpenIDProvider; import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; import no.nav.vedtak.sikkerhet.oidc.token.TokenProvider; @@ -18,8 +17,8 @@ public Supplier consumerIdFor(SikkerhetContext context) { return () -> TokenProvider.getConsumerIdFor(context); } - public Supplier tokenForSystem(OpenIDProvider provider, String scopes) { - return () -> TokenProvider.getTokenForSystem(provider, scopes); + public Supplier tokenForSystem(String scopes) { + return () -> TokenProvider.getTokenForSystem(scopes); } public Supplier adaptive(String scopes) { diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestRequest.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestRequest.java index 7a2075e11..d1e16b0c8 100644 --- a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestRequest.java +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/RestRequest.java @@ -11,11 +11,11 @@ import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; + import no.nav.vedtak.klient.http.HttpClientRequest; import no.nav.vedtak.log.mdc.MDCOperations; import no.nav.vedtak.mapper.json.DefaultJsonMapper; import no.nav.vedtak.sikkerhet.kontekst.SikkerhetContext; -import no.nav.vedtak.sikkerhet.oidc.config.OpenIDProvider; import no.nav.vedtak.sikkerhet.oidc.token.OpenIDToken; /** @@ -69,9 +69,6 @@ private RestRequest(HttpRequest.Builder builder, TokenFlow tokenConfig, String s if (!TokenFlow.NO_AUTH_NEEDED.equals(tokenConfig)) { this.authorization(selectTokenSupplier(tokenConfig, scopes)).validator(RestRequest::validateRestHeaders); } - if (TokenFlow.STS_ADD_CONSUMER.equals(tokenConfig)) { - this.consumerToken(); - } } // Serialize object to json @@ -147,11 +144,6 @@ private static HttpRequest.Builder getHttpRequestBuilder(Method method, URI targ }; } - private RestRequest consumerToken() { - delayedHeader(NavHeaders.HEADER_NAV_CONSUMER_TOKEN, () -> OIDC_AUTH_HEADER_PREFIX + CONTEXT_SUPPLIER.tokenForSystem(OpenIDProvider.STS, null).get().token()); - return this; - } - private RestRequest authorization(Supplier authToken) { delayedHeader(HttpHeaders.AUTHORIZATION, () -> OIDC_AUTH_HEADER_PREFIX + authToken.get().token()); return this; @@ -167,17 +159,17 @@ private static Supplier ensureCallId() { private static Supplier selectTokenSupplier(TokenFlow tokenConfig, String scopes) { return switch (tokenConfig) { case ADAPTIVE -> CONTEXT_SUPPLIER.adaptive(scopes); - case STS_CC, STS_ADD_CONSUMER -> CONTEXT_SUPPLIER.tokenForSystem(OpenIDProvider.STS, null); - case AZUREAD_CC -> CONTEXT_SUPPLIER.tokenForSystem(OpenIDProvider.AZUREAD, scopes); + case AZUREAD_CC -> CONTEXT_SUPPLIER.tokenForSystem(scopes); case NO_AUTH_NEEDED -> throw new IllegalArgumentException("No supplier needed"); }; } private static Supplier selectConsumerId(TokenFlow tokenConfig) { - return switch (tokenConfig) { - case STS_CC, STS_ADD_CONSUMER, AZUREAD_CC -> CONTEXT_SUPPLIER.consumerIdFor(SikkerhetContext.SYSTEM); - default -> CONTEXT_SUPPLIER.consumerIdForCurrentKontekst(); - }; + if (TokenFlow.AZUREAD_CC.equals(tokenConfig)) { + return CONTEXT_SUPPLIER.consumerIdFor(SikkerhetContext.SYSTEM); + } else { + return CONTEXT_SUPPLIER.consumerIdForCurrentKontekst(); + } } private static void validateRestHeaders(HttpRequest request) { diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/TokenFlow.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/TokenFlow.java index f814c2bf6..e71f11e51 100644 --- a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/TokenFlow.java +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/TokenFlow.java @@ -2,8 +2,6 @@ public enum TokenFlow { ADAPTIVE, // DWIM for targets accepting both azuread, sts tokens, and tokenx. STS->AzureCC - STS_CC, // Mot endepunkt som insisterer på STS og ikke aksepterer Azure - STS_ADD_CONSUMER, // Trengs inntil videre pga brreg.proxy AZUREAD_CC, // Mot endepunkt som bare støtter AzureCC, ikke AzureOBO-flow NO_AUTH_NEEDED; @@ -14,6 +12,6 @@ public boolean isAzureAD() { // Does the endpoint require a system client? public boolean isSystemRequired() { - return STS_CC.equals(this) || AZUREAD_CC.equals(this); + return AZUREAD_CC.equals(this); } } diff --git a/integrasjon/rest-klient/src/test/java/no/nav/vedtak/felles/integrasjon/rest/TestRestClientConfig.java b/integrasjon/rest-klient/src/test/java/no/nav/vedtak/felles/integrasjon/rest/TestRestClientConfig.java index a8d6a849f..2fe110f8d 100644 --- a/integrasjon/rest-klient/src/test/java/no/nav/vedtak/felles/integrasjon/rest/TestRestClientConfig.java +++ b/integrasjon/rest-klient/src/test/java/no/nav/vedtak/felles/integrasjon/rest/TestRestClientConfig.java @@ -30,7 +30,7 @@ void testCase_app_med_endpoint_property_som_har_verdi() { @Test void testCase_kun_application_uten_properties() { var config = RestConfig.forClient(TestRiskUtenEndpoint.class); - assertThat(config.tokenConfig()).isEqualTo(TokenFlow.STS_CC); + assertThat(config.tokenConfig()).isEqualTo(TokenFlow.AZUREAD_CC); assertThat(config.scopes()).isEqualTo(FpApplication.scopesFor(FpApplication.FPRISK)); assertThat(config.fpContextPath()).isEqualTo(URI.create(FpApplication.contextPathFor(FpApplication.FPRISK))); assertThat(config.endpoint()).isEqualTo(URI.create(FpApplication.contextPathFor(FpApplication.FPRISK))); @@ -79,7 +79,7 @@ private static class TestAbakusMedEndpointProperty { } - @RestClientConfig(tokenConfig = TokenFlow.STS_CC, application = FpApplication.FPRISK) + @RestClientConfig(tokenConfig = TokenFlow.AZUREAD_CC, application = FpApplication.FPRISK) private static class TestRiskUtenEndpoint { } From af19901c87830a0909917265f2ea5ac0be79d1ee Mon Sep 17 00:00:00 2001 From: Jens-Otto Larsen Date: Wed, 21 Aug 2024 10:00:06 +0200 Subject: [PATCH 2/2] Justere levetid assertions og cache-evict --- .../oidc/token/impl/MaskinportenAssertionGenerator.java | 2 +- .../sikkerhet/oidc/token/impl/MaskinportenTokenKlient.java | 2 +- .../sikkerhet/oidc/token/impl/TokenXAssertionGenerator.java | 2 +- .../sikkerhet/oidc/token/impl/TokenXExchangeKlient.java | 2 +- .../java/no/nav/vedtak/felles/integrasjon/rest/TokenFlow.java | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenAssertionGenerator.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenAssertionGenerator.java index afcbb9cb0..8c1a20b3c 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenAssertionGenerator.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenAssertionGenerator.java @@ -30,7 +30,7 @@ final class MaskinportenAssertionGenerator { String assertion(String scope, String resource) { try { var expirationTime = NumericDate.now(); - expirationTime.addSeconds(90); + expirationTime.addSeconds(60); JwtClaims claims = new JwtClaims(); claims.setIssuer(clientId); claims.setAudience(issuer); diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenTokenKlient.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenTokenKlient.java index a4dc2749c..69c08e23f 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenTokenKlient.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/MaskinportenTokenKlient.java @@ -47,7 +47,7 @@ private MaskinportenTokenKlient() { this.tokenEndpoint = URI.create(getMaskinportenProperty(MaskinportenProperty.MASKINPORTEN_TOKEN_ENDPOINT)); this.scopes = Arrays.stream(getMaskinportenProperty(MaskinportenProperty.MASKINPORTEN_SCOPES) .split("\\s+")).toList(); - this.obocache = new LRUCache<>(200, TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS)); + this.obocache = new LRUCache<>(200, TimeUnit.MILLISECONDS.convert(60, TimeUnit.MINUTES)); this.proxyUrl = ProxyProperty.getProxyIfFSS(); } diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXAssertionGenerator.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXAssertionGenerator.java index 001824cdc..0118444d4 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXAssertionGenerator.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXAssertionGenerator.java @@ -42,7 +42,7 @@ final class TokenXAssertionGenerator { String assertion() { try { var expirationTime = NumericDate.now(); - expirationTime.addSeconds(90); + expirationTime.addSeconds(60); JwtClaims claims = new JwtClaims(); claims.setSubject(clientId); diff --git a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java index e1a048849..18fc098ee 100644 --- a/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java +++ b/felles/oidc/src/main/java/no/nav/vedtak/sikkerhet/oidc/token/impl/TokenXExchangeKlient.java @@ -38,7 +38,7 @@ private TokenXExchangeKlient() { var provider = ConfigProvider.getOpenIDConfiguration(OpenIDProvider.TOKENX); this.assertionGenerator = new TokenXAssertionGenerator(provider.orElse(null)); this.tokenEndpoint = provider.map(OpenIDConfiguration::tokenEndpoint).orElse(null); - this.obocache = new LRUCache<>(2500, TimeUnit.MILLISECONDS.convert(110, TimeUnit.SECONDS)); + this.obocache = new LRUCache<>(2500, TimeUnit.MILLISECONDS.convert(60, TimeUnit.MINUTES)); } public static synchronized TokenXExchangeKlient instance() { diff --git a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/TokenFlow.java b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/TokenFlow.java index e71f11e51..c9eb84526 100644 --- a/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/TokenFlow.java +++ b/integrasjon/rest-klient/src/main/java/no/nav/vedtak/felles/integrasjon/rest/TokenFlow.java @@ -1,9 +1,9 @@ package no.nav.vedtak.felles.integrasjon.rest; public enum TokenFlow { - ADAPTIVE, // DWIM for targets accepting both azuread, sts tokens, and tokenx. STS->AzureCC + ADAPTIVE, // DWIM for kall til endepunkt velger azuread eller tokenx ut fra kontekst. AZUREAD_CC, // Mot endepunkt som bare støtter AzureCC, ikke AzureOBO-flow - NO_AUTH_NEEDED; + NO_AUTH_NEEDED; // Enten endepunkt som ikke krever autentisering eller otherAuthorizationSupplier (Maskinporten) // Does the endpoint require an Azure AD token? public boolean isAzureAD() {