Skip to content

Commit

Permalink
gplazma: print selected JWT claims for failed logins
Browse files Browse the repository at this point in the history
Motivation:

We deliberately do not log bearer tokens.  This follows security best-
practice, because log files are not always as protected as they should
be (e.g., copy-n-paste into a email or problem report), and this would
leak the token.

However, if an OIDC login fails and the bearer token is a JWT then there
may be useful information in the JWT's payload that would help diagnose
the problem.

Modification:

Update the LoginResultPrinter to identify bearer tokens that are JWT and
add support for printing selected claims from the JWT's payload.

Result:

If there is a login failure involving a OIDC JWT then potentially
important information is now extracted from the JWT and included in the
logged login failure report.

Target: master
Request: 9.0
Request: 8.2
Require-notes: yes
Require-book: no
Patch: https://rb.dcache.org/r/13975/
Acked-by: Tigran Mkrtchyan
  • Loading branch information
paulmillar authored and lemora committed May 12, 2023
1 parent bcf71f0 commit d096a5b
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.function.Function;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
Expand All @@ -44,6 +45,7 @@
import org.bouncycastle.asn1.x509.AttributeCertificate;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.dcache.auth.BearerTokenCredential;
import org.dcache.auth.FQAN;
import org.dcache.gplazma.configuration.ConfigurationItemControl;
import org.dcache.gplazma.monitor.LoginMonitor.Result;
Expand All @@ -59,6 +61,8 @@
import org.dcache.gplazma.monitor.LoginResult.SessionPluginResult;
import org.dcache.gplazma.monitor.LoginResult.SetDiff;
import org.dcache.gplazma.util.CertPaths;
import org.dcache.gplazma.util.JsonWebToken;
import org.dcache.util.TimeUtils;
import org.italiangrid.voms.VOMSAttribute;
import org.italiangrid.voms.asn1.VOMSACUtils;
import org.slf4j.Logger;
Expand Down Expand Up @@ -203,11 +207,66 @@ private List<String> buildInLines() {
private String print(Object credential) {
if (CertPaths.isX509CertPath(credential)) {
return print(CertPaths.getX509Certificates((CertPath) credential));
} else if (credential instanceof BearerTokenCredential) {
return print((BearerTokenCredential)credential);
} else {
return credential.toString();
}
}

private String print(BearerTokenCredential credential) {
String token = credential.getToken();

if (JsonWebToken.isCompatibleFormat(token)) {
try {
return print(new JsonWebToken(token));
} catch (IOException e) {
return "Bad JWT (" + e + "): " + credential;
}
} else {
return credential.toString();
}
}

private void appendPayloadSimpleClaim(StringBuilder sb, JsonWebToken jwt, String key) {
appendPayloadClaim(sb, jwt, key, Function.identity());
}

private void appendPayloadInstantClaim(StringBuilder sb, JsonWebToken jwt, String key) {
appendPayloadClaim(sb, jwt, key, s -> jwt.getPayloadInstant(key)
.map(i -> s + " --> " + TimeUtils.relativeTimestamp(i))
.orElse(s));
}

private void appendPayloadClaim(StringBuilder sb, JsonWebToken jwt, String key,
Function<String,String> valueEnhancer) {
jwt.getPayloadValueAsString(key)
.map(valueEnhancer::apply)
.map(v -> " +- " + key + ": " + v + "\n")
.ifPresent(sb::append);
}

private String print(JsonWebToken jwt) {
var sb = new StringBuilder();
sb.append("JWT bearer token:\n");
sb.append(" |\n");
appendPayloadSimpleClaim(sb, jwt, "iss");
appendPayloadSimpleClaim(sb, jwt, "jti");
appendPayloadSimpleClaim(sb, jwt, "sub");
appendPayloadSimpleClaim(sb, jwt, "scope");
appendPayloadSimpleClaim(sb, jwt, "authenticating_authority");
appendPayloadInstantClaim(sb, jwt, "auth_time");
appendPayloadInstantClaim(sb, jwt, "iat");
appendPayloadInstantClaim(sb, jwt, "nbf");
appendPayloadInstantClaim(sb, jwt, "exp");
appendPayloadSimpleClaim(sb, jwt, "aud");
appendPayloadSimpleClaim(sb, jwt, "azp");
appendPayloadSimpleClaim(sb, jwt, "client_id");
appendPayloadSimpleClaim(sb, jwt, "session_state");
appendPayloadSimpleClaim(sb, jwt, "sid");
return sb.toString();
}

private String print(X509Certificate[] certificates) {
StringBuilder sb = new StringBuilder();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package org.dcache.gplazma.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import static com.google.common.base.Preconditions.checkArgument;

import com.fasterxml.jackson.databind.JsonNode;
Expand Down Expand Up @@ -146,6 +147,17 @@ public Optional<String> getPayloadString(String key) {
.map(JsonNode::textValue);
}

public Optional<String> getPayloadValueAsString(String key) {
return Optional.ofNullable(payload.get(key))
.map(n -> {
try {
return mapper.writeValueAsString(n);
} catch (JsonProcessingException e) {
return "Bad JSON: " + e;
}
});
}

public Map<String,JsonNode> getPayloadMap() {
return Streams.stream(payload.fields())
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
Expand Down

0 comments on commit d096a5b

Please sign in to comment.