Skip to content

Commit 6b57061

Browse files
authored
[HWORKS-996] Only the Primary Hopsworks instance in the Active region should rotate and delete the signing key (#1465)
1 parent fc7461a commit 6b57061

File tree

1 file changed

+48
-22
lines changed

1 file changed

+48
-22
lines changed

hopsworks-common/src/main/java/io/hops/hopsworks/common/jwt/OneTimeJWTRotation.java

+48-22
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import javax.ejb.Timer;
3030
import javax.ejb.TimerConfig;
3131
import javax.ejb.TimerService;
32-
import java.util.Date;
32+
import java.util.concurrent.TimeUnit;
3333
import java.util.logging.Level;
3434
import java.util.logging.Logger;
3535

@@ -48,9 +48,12 @@ public class OneTimeJWTRotation {
4848
@PostConstruct
4949
public void init() {
5050
//number of milliseconds that must elapse between timer expiration notifications
51-
long intervalDuration = 24*3600000L; // 24 hour
52-
timer = timerService.createIntervalTimer(0, intervalDuration, new TimerConfig("Mark old JWT signing keys for " +
53-
"deletion", false));
51+
long intervalDuration = TimeUnit.HOURS.toMillis(24);
52+
53+
// The Info of the Timer **MUST** start with 'Mark'
54+
// Read comment of the @Timeout annotated method below
55+
timer = timerService.createIntervalTimer(0, intervalDuration, new TimerConfig("Mark old one-time JWT signing key",
56+
false));
5457
}
5558

5659
@PreDestroy
@@ -60,29 +63,52 @@ public void destroy() {
6063
}
6164
}
6265

66+
/**
67+
* Recurring Timer method which rotates the one-time JWT signing key.
68+
* It will rename the current to *_old and generate a new one
69+
* During validation of a JWT we lookup the signing key based on the ID,
70+
* so even if the name changes we will still be able to validate "older" JWTs
71+
*
72+
* Once we have generated a new signing key and renamed the old we create
73+
* a single action Timer which fires a little bit later. The reason is that we
74+
* want sessions which are using the old signing key to still be able to authenticate
75+
* for a short-while.
76+
*
77+
* Since we cannot have two methods in the same EJB with @Timeout annotation we take
78+
* different paths in the same method based on the **Info** field of the Timer.
79+
* The recurring Timer's Info will/must start with 'Mark' while the single action
80+
* will/must start with 'Sweep'
81+
*/
6382
@Timeout
64-
public void markOldSigningKeys() {
83+
public void rotateOneTimeJWTSigningKey(Timer timer) {
6584
if (!payaraClusterManager.amIThePrimary()) {
85+
LOGGER.log(Level.INFO, "I am not the Primary or Active region. Skip rotating one-time JWT signing key");
6686
return;
6787
}
68-
boolean marked = jWTController.markOldSigningKeys();
69-
if (marked) {
70-
//(60000 + 60000)*2 = 240000 milliseconds = 4 min
71-
long duration = (Constants.DEFAULT_EXPIRY_LEEWAY * 1000 + Constants.ONE_TIME_JWT_LIFETIME_MS) * 2;
72-
TimerConfig config = new TimerConfig();
73-
config.setInfo("Remove old JWT signing keys");
74-
config.setPersistent(false);
75-
timerService.createSingleActionTimer(duration, config);
76-
}
77-
}
7888

79-
@Timeout
80-
public void performTimeout(Timer timer) {
81-
try {
82-
jWTController.removeMarkedKeys();
83-
LOGGER.log(Level.INFO, "{0} timer event: {1}.", new Object[]{timer.getInfo(), new Date()});
84-
} catch (Exception e) {
85-
LOGGER.log(Level.SEVERE, "Got an exception while rotating one-time jwt", e);
89+
String timerInfo = (String) timer.getInfo();
90+
if (timerInfo.startsWith("Mark")) {
91+
LOGGER.log(Level.INFO, "Rotating one-time JWT signing key");
92+
boolean marked = jWTController.markOldSigningKeys();
93+
if (marked) {
94+
LOGGER.log(Level.INFO, "Marked old one-time JWT signing key, scheduling Sweeper");
95+
//(60000 + 60000)*2 = 240000 milliseconds = 4 min
96+
long duration = (Constants.DEFAULT_EXPIRY_LEEWAY * 1000 + Constants.ONE_TIME_JWT_LIFETIME_MS) * 2;
97+
TimerConfig config = new TimerConfig();
98+
// The Info of the Timer **MUST** start with 'Sweep'
99+
// Read comment of the method above
100+
config.setInfo("Sweep old one-time JWT signing key");
101+
config.setPersistent(false);
102+
timerService.createSingleActionTimer(duration, config);
103+
}
104+
} else if (timerInfo.startsWith("Sweep")) {
105+
LOGGER.log(Level.INFO, "Sweeping old one-time JWT signing key");
106+
try {
107+
jWTController.removeMarkedKeys();
108+
LOGGER.log(Level.INFO, "Deleted old one-time JWT signing key");
109+
} catch (Exception e) {
110+
LOGGER.log(Level.SEVERE, "Failed to delete old one-time JWT signing key", e);
111+
}
86112
}
87113
}
88114
}

0 commit comments

Comments
 (0)