Skip to content
This repository has been archived by the owner on Nov 22, 2023. It is now read-only.

Adding bookkeeping to keep secret series and secret content expiry in… #1177

Merged
merged 2 commits into from Jan 5, 2023

Conversation

mmontgomery-square
Copy link
Contributor

… sync

@mmontgomery-square mmontgomery-square requested a review from a team as a code owner December 21, 2022 18:09
@coveralls
Copy link

coveralls commented Dec 21, 2022

Coverage Status

Coverage increased (+0.04%) to 77.319% when pulling 58e8fb9 on alert-11203 into c474298 on master.

Comment on lines 172 to -181
public int setExpiration(long secretContentId, Instant expiration) {
Field<Long> minExpiration = decode()
.when(SECRETS_CONTENT.EXPIRY.eq(0L), val(expiration.getEpochSecond()))
.otherwise(least(SECRETS_CONTENT.EXPIRY, val(expiration.getEpochSecond())));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decode() appears to be a method which allows you to insert conditional logic as a value in a Jooq query. As a consequence of the edits below (reading the record first rather than doing a blind update), this has been replaced with plain Java.

Comment on lines +196 to +199
int secretsUpdated = dslContext.update(SECRETS)
.set(SECRETS.EXPIRY, updatedExpiry)
.where(SECRETS.CURRENT.eq(secretContentId))
.execute();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the block which keeps the series expiration and content expiration in sync. However, note that it only applies when the content being updated is the current version of a secret i.e. updating the expiration for non-current content has no effect on the series.

Comment on lines 219 to +226
public int setCurrentVersion(long secretId, long secretContentId, String updater, long now) {
long checkId;
Record1<Long> r = dslContext.select(SECRETS_CONTENT.SECRETID)
SecretsContentRecord r = dslContext
.select(
SECRETS_CONTENT.SECRETID,
SECRETS_CONTENT.EXPIRY)
.from(SECRETS_CONTENT)
.where(SECRETS_CONTENT.ID.eq(secretContentId))
.fetchOne();
.fetchOneInto(SECRETS_CONTENT);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it is possible to select arbitrary column values into a generic record, I think it's a lot more clear to always map the results into a properly typed record, even if not every field is populated.

Comment on lines 233 to 235

checkId = r.value1();
long checkId = r.getSecretid();
if (checkId != secretId) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanding on the above, I think getSecretId() is much more clear than value1().

Comment on lines 241 to 248
return dslContext.update(SECRETS)
.set(SECRETS.CURRENT, secretContentId)
.set(SECRETS.EXPIRY, r.getExpiry())
.set(SECRETS.UPDATEDBY, updater)
.set(SECRETS.UPDATEDAT, now)
.where(SECRETS.ID.eq(secretId))
.execute();
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we're explicitly setting the current content version, we always update the series expiration to match.

Comment on lines 250 to +258
public Optional<SecretSeries> getSecretSeriesById(long id) {
SecretsRecord r =
dslContext.fetchOne(SECRETS, SECRETS.ID.eq(id).and(SECRETS.CURRENT.isNotNull()));
SecretsRecord r = getSecretSeriesRecordById(id);
return Optional.ofNullable(r).map(secretSeriesMapper::map);
}

@VisibleForTesting
SecretsRecord getSecretSeriesRecordById(long id) {
return dslContext.fetchOne(SECRETS, SECRETS.ID.eq(id).and(SECRETS.CURRENT.isNotNull()));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far I've chosen not to expose the series-level expiration in the Java model (SecretSeries), since it's more of an internal optimization for the database. As a result, in order to examine the series-level expiration we need access to the database model's record object, which we previously didn't have.

Comment on lines +653 to +674
private static long randomExpiration() {
long expiration = 0;
Random random = new Random();

for (;;) {
expiration = random.nextLong();

if (expiration <= 0) {
continue;
}

try {
Instant.ofEpochSecond(expiration);
} catch (DateTimeException e) {
continue;
}

break;
}

return expiration;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the Keywhiz APIs bounce back and forth between long and Instant, we have to ensure that the expiration we generate is valid for both (this came up during a test where the random long value was outside the bounds of what's allowed for Instant seconds).

@mmontgomery-square mmontgomery-square merged commit b1f702b into master Jan 5, 2023
@mmontgomery-square mmontgomery-square deleted the alert-11203 branch January 5, 2023 20:48
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants