Skip to content

Commit

Permalink
Retry request on specific authentication errors. (#37)
Browse files Browse the repository at this point in the history
Retry request on specific authentication errors.

In some Instance Principal cases, the security token returned
from OCI Identity may have a very short expiration. This change removes
the expiration check, and allows a single retry on requests that
get specific authN errors.
It also adds trace-level logging of authentication token details.
  • Loading branch information
connelly38 authored Jan 5, 2023
1 parent bfe1d11 commit 437d4a2
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 19 deletions.
9 changes: 6 additions & 3 deletions driver/src/main/java/oracle/nosql/driver/http/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -717,9 +717,12 @@ public Result execute(Request kvRequest) {
throw new NoSQLException("Unexpected exception: " +
rae.getMessage(), rae);
} catch (InvalidAuthorizationException iae) {
/* allow a single retry on clock skew errors */
if (iae.getMessage().contains("clock skew") == false ||
kvRequest.getNumRetries() > 0) {
/*
* Allow a single retry for invalid/expired auth
* This includes "clock skew" errors
* This does not include permissions-related errors
*/
if (kvRequest.getNumRetries() > 0) {
/* same as NoSQLException below */
kvRequest.setRateLimitDelayedMs(rateDelayedMs);
statsControl.observeError(kvRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ private synchronized String refreshAndGetTokenInternal() {
}
SecurityToken token = getSecurityTokenFromIAM();
token.validate(minTokenLifetime);

/*
* Allow logging of token expiration details
*/
long tokenLifetime = token.getExpiryMS() - token.getCreationTime();
logTrace(logger, "New security token: lifetime=" + tokenLifetime +
", expiry=" + token.getExpiryMS() + ", creation=" +
token.getCreationTime());
if (tokenLifetime < minTokenLifetime) {
logTrace(logger, "token:\n" + token.getSecurityToken());
}

return token.getSecurityToken();
}

Expand Down Expand Up @@ -265,10 +277,17 @@ String getStringClaim(String key) {
return tokenClaims.get(key);
}

long getCreationTime() {
return creationTime;
}

long getExpiryMS() {
return expiryMS;
}

/*
* Validate the token, also check if the lifetime of token
* is longer than specified minimal lifetime, throws IAE
* if any validation fails.
* Validate the token.
* Throws IAE if any validation fails.
*/
void validate(long minTokenLifetime) {
if (tokenClaims == null) {
Expand All @@ -288,19 +307,9 @@ void validate(long minTokenLifetime) {
}

/*
* This is just a safety check, shouldn't happen in OCI environment,
* because security tokens always have more than one hour lifetime.
* It would only be thrown if short-living token being used or
* signature cache is misconfigured. To fix, must adjust the cache
* duration in both cases.
* Note: expiry check removed, as some tokens may have very short
* expirations.
*/
long tokenLifetime = expiryMS - creationTime;
if (tokenLifetime < minTokenLifetime) {
throw new IllegalArgumentException(
"Security token has less lifetime than signature cache " +
"duration, reduce signature cache duration less than " +
tokenLifetime + " milliseconds, token:\n" + securityToken);
}

/*
* Next compare the public key inside the JWT is the same
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ public QueryRequest setConsistency(Consistency consistency) {
*
* @return this
*
* @since 5.3.0
* @since 5.4.0
*/
public QueryRequest setDurability(Durability durability) {
setDurabilityInternal(durability);
Expand Down
13 changes: 13 additions & 0 deletions driver/src/main/java/oracle/nosql/driver/ops/TableResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ public String getDdl() {
* the on-premise service.
*
* @return the table OCID
*
* @since 5.4
*/
public String getTableId() {
return tableOcid;
Expand All @@ -122,6 +124,8 @@ public String getTableId() {
* Returns compartment id of the target table
*
* @return the compartment id if set
*
* @since 5.4
*/
public String getCompartmentId() {
return compartmentOrNamespace;
Expand All @@ -135,6 +139,8 @@ public String getCompartmentId() {
* is in a namespace.
*
* @return the namespace id if set
*
* @since 5.4
*/
public String getNamespace() {
return compartmentOrNamespace;
Expand All @@ -155,6 +161,8 @@ public String getTableName() {
* Returns the JSON-formatted schema of the table if available and null if
* not
* @return the schema
*
* @since 5.4
*/
public String getSchema() {
return schema;
Expand All @@ -177,6 +185,8 @@ public TableLimits getTableLimits() {
* if available, or null otherwise.
*
* @return the FreeFormTags
*
* @since 5.4
*/
public FreeFormTags getFreeFormTags() {
return freeFormTags;
Expand All @@ -189,6 +199,8 @@ public FreeFormTags getFreeFormTags() {
* if available, or null otherwise.
*
* @return the DefinedTags
*
* @since 5.4
*/
public DefinedTags getDefinedTags() {
return definedTags;
Expand All @@ -204,6 +216,7 @@ public DefinedTags getDefinedTags() {
* optimistic concurrency control mechanism.
*
* @return the matchETag
*
* @since 5.4
*/
public String getMatchETag() {
Expand Down

0 comments on commit 437d4a2

Please sign in to comment.