Skip to content

Commit

Permalink
ABAC-variant med native http og jackson (#1161)
Browse files Browse the repository at this point in the history
* ABAC-variant med native http og jackson

* Non static client and reader
  • Loading branch information
jolarsen authored Aug 9, 2022
1 parent be09e00 commit 6f26b3f
Show file tree
Hide file tree
Showing 19 changed files with 972 additions and 18 deletions.
4 changes: 4 additions & 0 deletions felles/abac/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
<groupId>no.nav.foreldrepenger.felles</groupId>
<artifactId>felles-log</artifactId>
</dependency>
<dependency>
<groupId>no.nav.foreldrepenger.felles</groupId>
<artifactId>felles-mapper</artifactId>
</dependency>
<!--Path annotation-->
<dependency>
<groupId>jakarta.ws.rs</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,12 @@ private static final Optional<String> getOneOf(AbacAttributtSamling attributter,
}

private static final EventClassId finnEventClassIdFra(String abacAction) {
switch (abacAction) {
case "read":
return AUDIT_ACCESS;
case "delete": /* Fall-through */
case "update":
return AUDIT_UPDATE;
case "create":
return AUDIT_CREATE;
default:
throw new IllegalArgumentException("Ukjent abacAction: " + abacAction);
}
return switch (abacAction) {
case "read" -> AUDIT_ACCESS; /* Fall-through */
case "delete", "update" -> AUDIT_UPDATE;
case "create" -> AUDIT_CREATE;
default -> throw new IllegalArgumentException("Ukjent abacAction: " + abacAction);
};
}

private static final List<String> allNonNullValues(PdpRequest pdpRequest, String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ public TokenType getTokenType() {
}

private String token() {
return switch (tokenType) {
case SAML -> "samlToken='MASKERT'";
default -> "jwtToken='" + maskerOidcToken(token) + '\'';
};
return TokenType.SAML.equals(tokenType) ? "samlToken='MASKERT'" : "jwtToken='" + maskerOidcToken(token) + '\'';
}

@Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Default;
import javax.inject.Inject;
import javax.inject.Named;

import no.nav.foreldrepenger.konfig.KonfigVerdi;

Expand All @@ -23,6 +24,7 @@ public class PepImpl implements Pep {
private final static String PIP = "pip.tjeneste.kan.kun.kalles.av.pdp.servicebruker";

private PdpKlient pdpKlient;
private PdpKlient pdp2Klient;
private PdpRequestBuilder builder;

private Set<String> pipUsers;
Expand All @@ -33,12 +35,22 @@ public PepImpl() {
}

@Inject
public PepImpl(PdpKlient pdpKlient,
public PepImpl(@Named("pdp1") PdpKlient pdpKlient,
TokenProvider tokenProvider,
PdpRequestBuilder pdpRequestBuilder,
AbacAuditlogger auditlogger,
@KonfigVerdi(value = "pip.users", required = false) String pipUsers) {
this(pdpKlient, null, tokenProvider, pdpRequestBuilder, auditlogger, pipUsers);
}

public PepImpl(PdpKlient pdpKlient,
PdpKlient pdp2Klient,
TokenProvider tokenProvider,
PdpRequestBuilder pdpRequestBuilder,
AbacAuditlogger auditlogger,
String pipUsers) {
this.pdpKlient = pdpKlient;
this.pdp2Klient = pdp2Klient;
this.builder = pdpRequestBuilder;
this.tokenProvider = tokenProvider;
this.auditlogger = auditlogger;
Expand All @@ -59,7 +71,11 @@ public Tilgangsbeslutning vurderTilgang(AbacAttributtSamling attributter) {
if (PIP.equals(attributter.getResource())) {
return vurderTilgangTilPipTjeneste(pdpRequest, attributter);
}
return pdpKlient.forespørTilgang(pdpRequest);
if (pdp2Klient != null) {
return pdp2Klient.forespørTilgang(pdpRequest);
} else {
return pdpKlient.forespørTilgang(pdpRequest);
}
}

protected Tilgangsbeslutning vurderTilgangTilPipTjeneste(PdpRequest pdpRequest, AbacAttributtSamling attributter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -35,6 +36,7 @@
import no.nav.vedtak.sikkerhet.pdp.xacml.XacmlResponseWrapper;

@ApplicationScoped
@Named("pdp1")
public class PdpKlientImpl implements PdpKlient {

private static final Environment ENV = Environment.current();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package no.nav.vedtak.sikkerhet.pdp2;

import no.nav.vedtak.sikkerhet.pdp2.xacml.XacmlRequestBuilder2;
import no.nav.vedtak.sikkerhet.pdp2.xacml.XacmlResponse;

public interface Pdp2Consumer {
XacmlResponse evaluate(XacmlRequestBuilder2 request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package no.nav.vedtak.sikkerhet.pdp2;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.Duration;
import java.util.Base64;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectReader;

import no.nav.foreldrepenger.konfig.KonfigVerdi;
import no.nav.vedtak.exception.TekniskException;
import no.nav.vedtak.log.mdc.MDCOperations;
import no.nav.vedtak.mapper.json.DefaultJsonMapper;
import no.nav.vedtak.sikkerhet.pdp2.xacml.XacmlRequestBuilder2;
import no.nav.vedtak.sikkerhet.pdp2.xacml.XacmlResponse;

@ApplicationScoped
public class PdpConsumerImpl implements Pdp2Consumer {

private static final String DEFAULT_ABAC_URL = "http://abac-foreldrepenger.teamabac/application/authorize";
private static final String PDP_ENDPOINT_URL_KEY = "abac.pdp.endpoint.url";
private static final String SYSTEMBRUKER_USERNAME = "systembruker.username";
private static final String SYSTEMBRUKER_PASSWORD = "systembruker.password"; // NOSONAR
private static final String MEDIA_TYPE = "application/xacml+json";
private static final Logger LOG = LoggerFactory.getLogger(PdpConsumerImpl.class);

private HttpClient client;
private ObjectReader reader;

private URI pdpUrl;
private String brukernavn;
private String basicCredentials;

PdpConsumerImpl() {
} // CDI

/*
* TODO(jol) vurder å hente det som injectes med ENV.getProperty - da kan hele saken gjøres static ....
*/
@Inject
public PdpConsumerImpl(@KonfigVerdi(value = PDP_ENDPOINT_URL_KEY, defaultVerdi = DEFAULT_ABAC_URL) URI pdpUrl,
@KonfigVerdi(SYSTEMBRUKER_USERNAME) String brukernavn,
@KonfigVerdi(SYSTEMBRUKER_PASSWORD) String passord) {
this.pdpUrl = pdpUrl;
this.brukernavn = brukernavn;
this.basicCredentials = basicCredentials(brukernavn, passord);
// TODO - vurder om bør settes static final?
this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
this.reader = DefaultJsonMapper.getObjectMapper().readerFor(XacmlResponse.class);
}

@Override
public XacmlResponse evaluate(XacmlRequestBuilder2 xacmlRequest) {
// TODO : hvilke headere trenger abac egentlig - utenom Auth og Content-type
var request = HttpRequest.newBuilder()
.header("Authorization", basicCredentials)
.header("Nav-Consumer-Id", brukernavn)
.header("Nav-Call-Id", MDCOperations.getCallId())
.header("Nav-Callid", MDCOperations.getCallId())
.header("Content-type", MEDIA_TYPE)
.timeout(Duration.ofSeconds(5))
.uri(pdpUrl)
.POST(HttpRequest.BodyPublishers.ofString(DefaultJsonMapper.toJson(xacmlRequest.build()), UTF_8))
.build();

try {
var response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString(UTF_8));
if (response == null || response.statusCode() == 401 || response.body() == null) {
LOG.info("ingen response fra PDP status = {}", response == null ? "null" : response.statusCode());
throw new TekniskException("F-157385", "Kunne ikke hente svar fra ABAC");
}
var resultat = reader.readValue(response.body(), XacmlResponse.class);
LOG.trace("PDP2 svar {}", resultat);
return resultat;
} catch (JsonProcessingException e) {
throw new TekniskException("F-208314", "Kunne ikke deserialisere objekt til JSON", e);
} catch (IOException e) {
throw new TekniskException("F-091324", "Uventet IO-exception mot PDP", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new TekniskException("F-432938", "InterruptedException ved henting av token", e);
}
}

private static String basicCredentials(String username, String password) {
return "Basic " + Base64.getEncoder().encodeToString(String.format("%s:%s", username, password).getBytes(UTF_8));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package no.nav.vedtak.sikkerhet.pdp2;

import static no.nav.foreldrepenger.konfig.Environment.NAIS_APP_NAME;
import static no.nav.vedtak.sikkerhet.abac.NavAbacCommonAttributter.ENVIRONMENT_FELLES_OIDC_TOKEN_BODY;
import static no.nav.vedtak.sikkerhet.abac.NavAbacCommonAttributter.ENVIRONMENT_FELLES_PEP_ID;
import static no.nav.vedtak.sikkerhet.abac.NavAbacCommonAttributter.ENVIRONMENT_FELLES_SAML_TOKEN;
import static no.nav.vedtak.sikkerhet.abac.NavAbacCommonAttributter.ENVIRONMENT_FELLES_TOKENX_TOKEN_BODY;

import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Base64;
import java.util.List;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.nimbusds.jwt.SignedJWT;

import no.nav.foreldrepenger.konfig.Environment;
import no.nav.vedtak.exception.TekniskException;
import no.nav.vedtak.log.util.LoggerUtils;
import no.nav.vedtak.sikkerhet.abac.AbacIdToken;
import no.nav.vedtak.sikkerhet.abac.AbacResultat;
import no.nav.vedtak.sikkerhet.abac.Decision;
import no.nav.vedtak.sikkerhet.abac.PdpKlient;
import no.nav.vedtak.sikkerhet.abac.PdpRequest;
import no.nav.vedtak.sikkerhet.abac.Tilgangsbeslutning;
import no.nav.vedtak.sikkerhet.pdp2.xacml.Advice;
import no.nav.vedtak.sikkerhet.pdp2.xacml.XacmlAttributeSet;
import no.nav.vedtak.sikkerhet.pdp2.xacml.XacmlRequestBuilder2;
import no.nav.vedtak.sikkerhet.pdp2.xacml.XacmlResponse;
import no.nav.vedtak.sikkerhet.pdp2.xacml.XacmlResponseMapper;

@ApplicationScoped
@Named("pdp2")
public class PdpKlientImpl implements PdpKlient {

private static final Environment ENV = Environment.current();
private static final Logger LOG = LoggerFactory.getLogger(PdpKlientImpl.class);
private XacmlRequestBuilder2Tjeneste xamlRequestBuilderTjeneste;

private Pdp2Consumer pdp;

public PdpKlientImpl() {
}

@Inject
public PdpKlientImpl(Pdp2Consumer pdp, XacmlRequestBuilder2Tjeneste xamlRequestBuilderTjeneste) {
this.pdp = pdp;
this.xamlRequestBuilderTjeneste = xamlRequestBuilderTjeneste;
}

@Override
public Tilgangsbeslutning forespørTilgang(PdpRequest req) {
var builder = xamlRequestBuilderTjeneste.lagXacmlRequestBuilder2(req);
leggPåTokenInformasjon(builder, req);
var response = pdp.evaluate(builder);
var hovedresultat = resultatFraResponse(response);
return new Tilgangsbeslutning(hovedresultat, XacmlResponseMapper.getDecisions(response), req);
}

static void leggPåTokenInformasjon(XacmlRequestBuilder2 builder, PdpRequest req) {
var attrs = new XacmlAttributeSet();
attrs.addAttribute(ENVIRONMENT_FELLES_PEP_ID, getPepId());
var idToken = AbacIdToken.class.cast(req.get(ENVIRONMENT_AUTH_TOKEN));
switch (idToken.getTokenType()) {
case OIDC:
String key = ENVIRONMENT_FELLES_OIDC_TOKEN_BODY;
LOG.trace("Legger ved token med type oidc på {}", key);
try {
attrs.addAttribute(key, SignedJWT.parse(idToken.getToken()).getPayload().toBase64URL().toString());
} catch (ParseException e) {
throw new IllegalArgumentException("Ukjent token type");
}
break;
case TOKENX:
String keyX = ENVIRONMENT_FELLES_TOKENX_TOKEN_BODY;
LOG.trace("Legger IKKE ved token med type tokenX på {}", keyX);
/*
try {
attrs.addAttribute(keyX,
SignedJWT.parse(idToken.getToken()).getPayload().toBase64URL().toString());
} catch (ParseException e) {
throw new IllegalArgumentException("Ukjent token type");
}
*/
break;
case SAML:
LOG.trace("Legger på token med type saml");
attrs.addAttribute(ENVIRONMENT_FELLES_SAML_TOKEN, base64encode(idToken.getToken()));
break;
}

builder.addEnvironmentAttributeSet(attrs);
}

private static String base64encode(String samlToken) {
return Base64.getEncoder().encodeToString(samlToken.getBytes(StandardCharsets.UTF_8));
}

private static AbacResultat resultatFraResponse(XacmlResponse response) {
var decisions = XacmlResponseMapper.getDecisions(response);

for (var decision : decisions) {
if (decision == Decision.Indeterminate) {
throw new TekniskException("F-080281",
String.format("Decision %s fra PDP, dette skal aldri skje. Full JSON response: %s", decision, response));
}
}

var biasedDecision = createAggregatedDecision(decisions);
handlObligation(response);

if (biasedDecision == Decision.Permit) {
return AbacResultat.GODKJENT;
}

var denyAdvice = XacmlResponseMapper.getAdvice(response);

if (LOG.isDebugEnabled()) {
LOG.debug("Deny fra PDP, advice var: {}", LoggerUtils.toStringWithoutLineBreaks(denyAdvice));
}
if (denyAdvice.contains(Advice.DENY_KODE_6)) {
return AbacResultat.AVSLÅTT_KODE_6;
}
if (denyAdvice.contains(Advice.DENY_KODE_7)) {
return AbacResultat.AVSLÅTT_KODE_7;
}
if (denyAdvice.contains(Advice.DENY_EGEN_ANSATT)) {
return AbacResultat.AVSLÅTT_EGEN_ANSATT;
}
return AbacResultat.AVSLÅTT_ANNEN_ÅRSAK;
}

private static Decision createAggregatedDecision(List<Decision> decisions) {
for (var decision : decisions) {
if (decision != Decision.Permit)
return Decision.Deny;
}
return Decision.Permit;
}

private static void handlObligation(XacmlResponse response) {
var obligations = XacmlResponseMapper.getObligations(response);
if (!obligations.isEmpty()) {
throw new TekniskException("F-576027", String.format("Mottok ukjente obligations fra PDP: %s", obligations));
}
}

private static String getPepId() {
return ENV.getProperty(NAIS_APP_NAME, "local-app");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package no.nav.vedtak.sikkerhet.pdp2;

import no.nav.vedtak.sikkerhet.abac.PdpRequest;
import no.nav.vedtak.sikkerhet.pdp2.xacml.XacmlRequestBuilder2;

public interface XacmlRequestBuilder2Tjeneste {
/**
* Legger på de attributter som trengs for vurdering av abac-policy
*
* @param pdpRequest attributter som systemet har plukket ut som relevant for
* requestet
* @return XacmlRequestBuilder
*/
XacmlRequestBuilder2 lagXacmlRequestBuilder2(PdpRequest req);
}
Loading

0 comments on commit 6f26b3f

Please sign in to comment.