Skip to content

Commit 488a00a

Browse files
Extract package name from JWT token
The dynamic log level feature was extend by two filters for log4j2 and logback, that allow filtering on package names. To configure what packages should be eligible for dynamic log level change the JWT token can now contain a field `packages`. When the new filters are used, the log level change is only applied to those packages.
1 parent aff997d commit 488a00a

File tree

4 files changed

+114
-99
lines changed

4 files changed

+114
-99
lines changed

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/DynamicLogLevelProcessor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ public void copyDynamicLogLevelToMDC(HttpServletRequest httpRequest) {
3939
try {
4040
DecodedJWT jwt = tokenDecoder.validateAndDecodeToken(logLevelToken);
4141
String dynamicLogLevel = jwt.getClaim("level").asString();
42+
String packages = jwt.getClaim("packages").asString();
4243
if (ALLOWED_DYNAMIC_LOGLEVELS.contains(dynamicLogLevel)) {
4344
MDC.put(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_KEY, dynamicLogLevel);
45+
MDC.put(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_PREFIXES, packages);
4446
} else {
4547
throw new DynamicLogLevelException("Dynamic Log-Level [" + dynamicLogLevel +
4648
"] provided in header is not valid. Allowed Values are " +

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/TokenCreator.java

Lines changed: 102 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -19,107 +19,112 @@
1919
import org.apache.commons.lang3.StringUtils;
2020

2121
import com.auth0.jwt.JWT;
22+
import com.auth0.jwt.JWTCreator.Builder;
2223
import com.auth0.jwt.algorithms.Algorithm;
2324

2425
public class TokenCreator {
2526

26-
private static final List<String> ALLOWED_DYNAMIC_LOGLEVELS = Arrays.asList("TRACE", "DEBUG", "INFO", "WARN",
27-
"ERROR");
28-
29-
/**
30-
* Run this application locally in order to generate valid dynamic log level
31-
* JWT tokens which enable you to change the log level threshold on your
32-
* CF-Application for a single thread.
33-
*/
34-
public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException,
35-
DynamicLogLevelException, InvalidKeySpecException {
36-
37-
/*
38-
* PLEASE PROVIDE THIS INFORMATION ***********************************
39-
*/
40-
// Replace with email address
41-
String issuer = "firstname.lastname@sap.com";
42-
// Replace with the log level that should be transmitted via the token
43-
// Valid log level thresholds are:
44-
// "TRACE", "DEBUG", "INFO", "WARN", "ERROR"
45-
String level = "TRACE";
46-
// Set a validity period in days
47-
long validityPeriodInDays = 2;
48-
// If available provide Base64 encoded private key here:
49-
String privateKey = "";
50-
// If available provide Base64 encoded private key here:
51-
String publicKey = "";
52-
// (If no keys are provided, new keys will be generated)
53-
/*
54-
* ********************************************************************
55-
*/
56-
57-
KeyPair keyPair;
58-
59-
if (StringUtils.isNotBlank(privateKey)) {
60-
keyPair = new KeyPair(publicKeyConverter(publicKey), privateKeyConverter(privateKey));
61-
}
62-
63-
else {
64-
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
27+
private static final List<String> ALLOWED_DYNAMIC_LOGLEVELS = Arrays.asList("TRACE", "DEBUG", "INFO", "WARN",
28+
"ERROR");
29+
30+
/**
31+
* Run this application locally in order to generate valid dynamic log level JWT
32+
* tokens which enable you to change the log level threshold on your
33+
* CF-Application for a single thread.
34+
*/
35+
public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException,
36+
DynamicLogLevelException, InvalidKeySpecException {
37+
38+
/*
39+
* PLEASE PROVIDE THIS INFORMATION ***********************************
40+
*/
41+
// Replace with email address
42+
String issuer = "firstname.lastname@sap.com";
43+
// Replace with the log level that should be transmitted via the token
44+
// Valid log level thresholds are:
45+
// "TRACE", "DEBUG", "INFO", "WARN", "ERROR"
46+
String level = "TRACE";
47+
// Replace with the packages that should be transmitted via the token
48+
// Multiple packages should be separated by a comma.
49+
String packages = "";
50+
// Set a validity period in days
51+
long validityPeriodInDays = 2;
52+
// If available provide Base64 encoded private key here:
53+
String privateKey = "";
54+
// If available provide Base64 encoded private key here:
55+
String publicKey = "";
56+
// (If no keys are provided, new keys will be generated)
57+
/*
58+
* ********************************************************************
59+
*/
60+
61+
KeyPair keyPair;
62+
63+
if (StringUtils.isNotBlank(privateKey)) {
64+
keyPair = new KeyPair(publicKeyConverter(publicKey), privateKeyConverter(privateKey));
65+
}
66+
67+
else {
68+
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
6569
keyGen.initialize(2048);
66-
keyPair = keyGen.generateKeyPair();
67-
// keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
68-
}
69-
privateKey = DatatypeConverter.printBase64Binary(keyPair.getPrivate().getEncoded());
70-
publicKey = DatatypeConverter.printBase64Binary(keyPair.getPublic().getEncoded());
71-
Date issuedAt = new Date();
72-
Date expiresAt = new Date(new Date().getTime() + validityPeriodInDays * 86400000);
73-
String token = TokenCreator.createToken(keyPair, issuer, issuedAt, expiresAt, level);
74-
75-
System.out.println("You successfully created a dynamic log level token with log level " + level + "!");
76-
System.out.println();
77-
System.out.println("Your private key is:");
78-
System.out.println(privateKey);
79-
System.out.println("Your public key is:");
80-
System.out.println(publicKey);
81-
System.out.println("Your JWT token with log level " + level + " is:");
82-
System.out.println(token);
83-
System.out.println();
84-
System.out.println("Please copy and save token and keys for later usage. The JWT token can now be written");
85-
System.out.println("to an HTTP header in order to change the corresponding request's log level to " + level);
86-
System.out.println("For token validation, the public key must be added to the environment of the application.");
87-
System.out.println("In order to generate a new token with specific keys, the variables privateKey and publicKey");
88-
System.out.println("can be instantiated with these keys");
89-
90-
}
91-
92-
public static String createToken(KeyPair keyPair, String issuer, Date issuedAt, Date expiresAt, String level)
93-
throws NoSuchAlgorithmException,
94-
NoSuchProviderException,
95-
DynamicLogLevelException {
96-
Algorithm rsa256 = Algorithm.RSA256((RSAPublicKey) keyPair.getPublic(), (RSAPrivateKey) keyPair.getPrivate());
97-
if (ALLOWED_DYNAMIC_LOGLEVELS.contains(level)) {
98-
return JWT.create().withIssuer(issuer).//
99-
withIssuedAt(issuedAt). //
100-
withExpiresAt(expiresAt).//
101-
withClaim("level", level).sign(rsa256);
102-
} else {
103-
throw new DynamicLogLevelException("Dynamic Log-Level [" + level +
104-
"] provided in header is not valid. Allowed Values are " +
105-
ALLOWED_DYNAMIC_LOGLEVELS.toString());
106-
}
107-
}
108-
109-
private static RSAPublicKey publicKeyConverter(String pemKey) throws NoSuchAlgorithmException,
110-
InvalidKeySpecException {
111-
byte[] keyBytes = DatatypeConverter.parseBase64Binary(pemKey);
112-
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
113-
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
114-
return (RSAPublicKey) keyFactory.generatePublic(spec);
115-
}
116-
117-
private static RSAPrivateKey privateKeyConverter(String pemKey) throws NoSuchAlgorithmException,
118-
InvalidKeySpecException {
119-
byte[] keyBytes = DatatypeConverter.parseBase64Binary(pemKey);
120-
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
121-
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
122-
return (RSAPrivateKey) keyFactory.generatePrivate(spec);
123-
}
70+
keyPair = keyGen.generateKeyPair();
71+
// keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
72+
}
73+
privateKey = DatatypeConverter.printBase64Binary(keyPair.getPrivate().getEncoded());
74+
publicKey = DatatypeConverter.printBase64Binary(keyPair.getPublic().getEncoded());
75+
Date issuedAt = new Date();
76+
Date expiresAt = new Date(new Date().getTime() + validityPeriodInDays * 86400000);
77+
String token = TokenCreator.createToken(keyPair, issuer, issuedAt, expiresAt, level, packages);
78+
79+
System.out.println("You successfully created a dynamic log level token with log level " + level
80+
+ " and packages " + packages + "!");
81+
System.out.println();
82+
System.out.println("Your private key is:");
83+
System.out.println(privateKey);
84+
System.out.println("Your public key is:");
85+
System.out.println(publicKey);
86+
System.out.println("Your JWT token with log level " + level + " is:");
87+
System.out.println(token);
88+
System.out.println();
89+
System.out.println("Please copy and save token and keys for later usage. The JWT token can now be written");
90+
System.out.println("to an HTTP header in order to change the corresponding request's log level to " + level);
91+
System.out.println("For token validation, the public key must be added to the environment of the application.");
92+
System.out
93+
.println("In order to generate a new token with specific keys, the variables privateKey and publicKey");
94+
System.out.println("can be instantiated with these keys");
95+
96+
}
97+
98+
public static String createToken(KeyPair keyPair, String issuer, Date issuedAt, Date expiresAt, String level,
99+
String packages) throws NoSuchAlgorithmException, NoSuchProviderException, DynamicLogLevelException {
100+
Algorithm rsa256 = Algorithm.RSA256((RSAPublicKey) keyPair.getPublic(), (RSAPrivateKey) keyPair.getPrivate());
101+
if (ALLOWED_DYNAMIC_LOGLEVELS.contains(level)) {
102+
Builder builder = JWT.create().withIssuer(issuer).//
103+
withIssuedAt(issuedAt). //
104+
withExpiresAt(expiresAt).//
105+
withClaim("level", level);
106+
builder = StringUtils.isNotBlank(packages) ? builder.withClaim("packages", packages) : builder;
107+
return builder.withClaim("packages", packages).sign(rsa256);
108+
} else {
109+
throw new DynamicLogLevelException("Dynamic Log-Level [" + level
110+
+ "] provided in header is not valid. Allowed Values are " + ALLOWED_DYNAMIC_LOGLEVELS.toString());
111+
}
112+
}
113+
114+
private static RSAPublicKey publicKeyConverter(String pemKey)
115+
throws NoSuchAlgorithmException, InvalidKeySpecException {
116+
byte[] keyBytes = DatatypeConverter.parseBase64Binary(pemKey);
117+
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
118+
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
119+
return (RSAPublicKey) keyFactory.generatePublic(spec);
120+
}
121+
122+
private static RSAPrivateKey privateKeyConverter(String pemKey)
123+
throws NoSuchAlgorithmException, InvalidKeySpecException {
124+
byte[] keyBytes = DatatypeConverter.parseBase64Binary(pemKey);
125+
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
126+
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
127+
return (RSAPrivateKey) keyFactory.generatePrivate(spec);
128+
}
124129

125130
}

cf-java-logging-support-servlet/src/test/java/com/sap/hcp/cf/logging/servlet/dynlog/DynamicLogLevelProcessorTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public void setup() throws NoSuchAlgorithmException, NoSuchProviderException, Dy
3939
String keyBase64 = DatatypeConverter.printBase64Binary(keyPair.getPublic().getEncoded());
4040
Date issuedAt = new Date();
4141
Date expiresAt = new Date(new Date().getTime() + 10000);
42-
String token = TokenCreator.createToken(keyPair, "issuer", issuedAt, expiresAt, "TRACE");
42+
String token = TokenCreator.createToken(keyPair, "issuer", issuedAt, expiresAt, "TRACE", "myPrefix");
4343
when(environment.getVariable("DYN_LOG_LEVEL_KEY")).thenReturn(keyBase64);
4444
when(environment.getVariable("DYN_LOG_HEADER")).thenReturn("SAP-LOG-LEVEL");
4545
when(httpRequest.getHeader("SAP-LOG-LEVEL")).thenReturn(token);
@@ -62,4 +62,11 @@ public void testDeleteDynamicLogLevelFromMDC() throws Exception {
6262
processor.removeDynamicLogLevelFromMDC();
6363
assertEquals(null, MDC.get(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_KEY));
6464
}
65+
66+
@Test
67+
public void testCopyDynamicLogPackagesToMDC() throws Exception {
68+
processor.copyDynamicLogLevelToMDC(httpRequest);
69+
assertEquals("myPrefix", MDC.get(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_PREFIXES));
70+
71+
}
6572
}

cf-java-logging-support-servlet/src/test/java/com/sap/hcp/cf/logging/servlet/dynlog/TokenDecoderTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ public void setup() throws NoSuchAlgorithmException, NoSuchProviderException, Dy
2626
invalidKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
2727
Date issuedAt = new Date();
2828
Date expiresAt = new Date(new Date().getTime() + 10000);
29-
token = TokenCreator.createToken(validKeyPair, "issuer", issuedAt, expiresAt, "TRACE");
29+
token = TokenCreator.createToken(validKeyPair, "issuer", issuedAt, expiresAt, "TRACE", "myPrefix");
3030
}
3131

3232
@Test
3333
public void testTokenContent() throws Exception {
3434
TokenDecoder tokenDecoder = new TokenDecoder((RSAPublicKey) validKeyPair.getPublic());
3535
DecodedJWT jwt = tokenDecoder.validateAndDecodeToken(token);
3636
assertEquals(jwt.getClaim("level").asString(), "TRACE");
37+
assertEquals(jwt.getClaim("packages").asString(), "myPrefix");
3738
}
3839

3940
@Test(expected = DynamicLogLevelException.class)

0 commit comments

Comments
 (0)