Skip to content

feat(identity): Temporary credentials jwt#6213

Draft
salvatore-coppola wants to merge 5 commits intodevelopfrom
feature/container-temporary-credentials-jwt
Draft

feat(identity): Temporary credentials jwt#6213
salvatore-coppola wants to merge 5 commits intodevelopfrom
feature/container-temporary-credentials-jwt

Conversation

@salvatore-coppola
Copy link
Copy Markdown
Member

This pull request introduces support for JWT-based authentication for container identity integration, alongside the existing password-based approach. It adds new interfaces and data structures for token management, updates the container provider to handle tokens, and enhances configuration options to allow selection of authentication mode. The most important changes are grouped below:

JWT Token-based Authentication Support:

  • Added new IdentityTokenService interface, TokenPair, and VerifiedAccessToken classes to the API for issuing, refreshing, verifying, and revoking JWT tokens. (kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/identity/IdentityTokenService.java [1] TokenPair.java [2] VerifiedAccessToken.java [3]
  • Updated ContainerInstance to support JWT authentication: provisions JWT tokens as files, injects relevant environment variables and volumes, and manages token lifecycle (including renewal scheduling and cleanup). (ContainerInstance.java [1] [2] [3] [4] [5] [6]

Configuration and Dependency Injection:

API Enhancements:

These changes collectively enable secure, flexible authentication for containers, with a focus on modern token-based workflows and improved configurability.

@salvatore-coppola salvatore-coppola self-assigned this Apr 9, 2026
@salvatore-coppola salvatore-coppola force-pushed the feature/container-temporary-credentials-jwt branch from 1172437 to d771e89 Compare April 9, 2026 09:22
@MMaiero MMaiero requested review from Copilot and marcellorinaldo and removed request for MMaiero April 9, 2026 09:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds JWT token-pair authentication alongside the existing session/password flow, enabling container identity integration and REST access via bearer tokens, backed by a new core IdentityTokenService implementation and config flags to enable/disable JWT support.

Changes:

  • Introduces IdentityTokenService API + core implementation for issuing/refreshing/verifying/revoking JWT token pairs and identity revision tracking.
  • Extends REST auth with JWT bearer provider and /session/v2 token endpoints + config toggle auth.jwt.enabled.
  • Updates container identity integration to provision tokens via secret files (with scheduled renewal) and adds container.identity.auth.mode.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
kura/test/org.eclipse.kura.rest.provider.test/src/main/java/org/eclipse/kura/rest/provider/test/RestServiceTest.java Adds REST tests for /session/v2 token endpoints and bearer auth.
kura/test/org.eclipse.kura.rest.cloudconnection.provider.test/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/test/CloudConnectionEndpointsTest.java Adjusts pub/sub instance lifecycle tests and service tracking.
kura/test/org.eclipse.kura.container.provider.test/src/test/java/org/eclipse/kura/container/provider/ContainerIdentityIntegrationTest.java Updates container identity integration tests to expect token-file injection instead of password env var.
kura/test-util/org.eclipse.kura.core.testutil/src/main/java/org/eclipse/kura/core/testutil/requesthandler/MqttTransport.java Makes embedded MQTT broker startup/connection more resilient (dynamic port, reconnect logic).
kura/org.eclipse.kura.rest.provider/src/main/java/org/eclipse/kura/internal/rest/provider/RestServiceOptions.java Adds auth.jwt.enabled option handling.
kura/org.eclipse.kura.rest.provider/src/main/java/org/eclipse/kura/internal/rest/provider/RestService.java Wires optional IdentityTokenService and conditionally registers JWT auth provider and token endpoints.
kura/org.eclipse.kura.rest.provider/src/main/java/org/eclipse/kura/internal/rest/auth/SessionRestServiceConstants.java Introduces /session/v2 paths and refactors v1 constants under /session/v1.
kura/org.eclipse.kura.rest.provider/src/main/java/org/eclipse/kura/internal/rest/auth/SessionRestService.java Adds /v2 login/refresh/logout token endpoints backed by IdentityTokenService.
kura/org.eclipse.kura.rest.provider/src/main/java/org/eclipse/kura/internal/rest/auth/JwtAuthenticationProvider.java New bearer-token AuthenticationProvider using IdentityTokenService.verifyAccessToken.
kura/org.eclipse.kura.rest.provider/src/main/java/org/eclipse/kura/internal/rest/auth/dto/TokenAuthenticationResponseDTO.java New response DTO for token-pair responses.
kura/org.eclipse.kura.rest.provider/src/main/java/org/eclipse/kura/internal/rest/auth/dto/RefreshTokenDTO.java New DTO for refresh-token requests.
kura/org.eclipse.kura.rest.provider/OSGI-INF/metatype/org.eclipse.kura.internal.rest.provider.RestService.xml Adds metatype entry for auth.jwt.enabled.
kura/org.eclipse.kura.core.identity/src/main/java/org/eclipse/kura/core/identity/IdentityTokenServiceImpl.java Implements JWT issuance/verification/refresh with refresh-token rotation tracking and keystore-backed signing.
kura/org.eclipse.kura.core.identity/src/main/java/org/eclipse/kura/core/identity/IdentityServiceImpl.java Adds identity revision tracking + getIdentityRevision for token invalidation use cases.
kura/org.eclipse.kura.core.identity/META-INF/MANIFEST.MF Adds imports needed by the token service implementation (keystore, Gson).
kura/org.eclipse.kura.container.provider/src/main/java/org/eclipse/kura/container/provider/ContainerInstanceOptions.java Adds container.identity.auth.mode option (jwt/password).
kura/org.eclipse.kura.container.provider/src/main/java/org/eclipse/kura/container/provider/ContainerInstance.java Provisions JWT tokens as files/volumes/env vars and schedules access-token renewal via refresh tokens.
kura/org.eclipse.kura.container.provider/OSGI-INF/metatype/org.eclipse.kura.container.provider.ContainerInstance.xml Adds metatype entry for container.identity.auth.mode.
kura/org.eclipse.kura.container.provider/OSGI-INF/containerinstance.xml Adds DS reference injection for IdentityTokenService.
kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/identity/VerifiedAccessToken.java New API type returned by token verification.
kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/identity/TokenPair.java New API type representing access/refresh tokens + expiry + family id.
kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/identity/IdentityTokenService.java New API for issuing/refreshing/verifying/revoking tokens.
kura/org.eclipse.kura.api/src/main/java/org/eclipse/kura/identity/IdentityService.java Adds getIdentityRevision API used for token invalidation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -231,6 +269,14 @@ private void updateBuiltinAuthenticationProviders(final RestServiceOptions optio
if (options.isSessionManagementEnabled()) {
bindAuthenticationProvider(this.sessionAuthenticationProvider);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

updateBuiltinAuthenticationProviders binds sessionAuthenticationProvider when session.management.enabled is true, but never unbinds it when the option is later set to false. This makes session.management.enabled=false ineffective after a previous true state (sessions can still authenticate). Add an else branch that unbinds sessionAuthenticationProvider when session management is disabled (mirroring the basic/certificate handling).

Suggested change
bindAuthenticationProvider(this.sessionAuthenticationProvider);
bindAuthenticationProvider(this.sessionAuthenticationProvider);
} else {
unbindAuthenticationProvider(this.sessionAuthenticationProvider);

Copilot uses AI. Check for mistakes.
Comment on lines +567 to +581
private void writeTokenFile(final Path tokenFile, final String tokenValue) throws IOException {
final Set<PosixFilePermission> permissions = Set.of(PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE);

try {
Files.createFile(tokenFile, PosixFilePermissions.asFileAttribute(permissions));
} catch (FileAlreadyExistsException e) {
Files.setPosixFilePermissions(tokenFile, permissions);
} catch (UnsupportedOperationException e) {
logger.debug("POSIX permissions not supported for {}", tokenFile, e);
}

Files.write(tokenFile, tokenValue.getBytes(StandardCharsets.UTF_8),
StandardOpenOption.TRUNCATE_EXISTING);
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

writeTokenFile swallows UnsupportedOperationException from Files.createFile(..., PosixFilePermissions.asFileAttribute(...)), but then immediately calls Files.write(..., TRUNCATE_EXISTING). On non-POSIX file systems this leaves the file uncreated and Files.write will fail with NoSuchFileException. Also, in the FileAlreadyExistsException branch Files.setPosixFilePermissions(...) can throw UnsupportedOperationException and currently isn’t handled. Consider writing with CREATE/TRUNCATE_EXISTING and applying POSIX permissions in a separate best-effort block so token provisioning works across supported file systems.

Copilot uses AI. Check for mistakes.
Comment on lines +227 to +241
final JsonObject header = parseJsonObject(parts[0]);
final JsonObject payload = parseJsonObject(parts[1]);

final String algorithm = payloadValue(header, "alg");
if (!JWT_ALG.equals(algorithm)) {
throw new KuraException(KuraErrorCode.SECURITY_EXCEPTION, "Unsupported JWT algorithm");
}

final String kid = payloadValue(header, "kid");
final String signingInput = parts[0] + "." + parts[1];
verifySignature(signingInput, parts[2], getVerificationPublicKey(kid));

validateClaims(payload);

return new DecodedJwt(payload);
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

decodeAndVerify can throw unchecked exceptions (e.g., invalid base64url / invalid JSON in parseJsonObject, or JsonSyntaxException) that are not wrapped as KuraException. This is particularly problematic for refreshTokenPair, which only declares/handles KuraException, and for the REST refresh endpoint which catches KuraException only—malformed tokens could surface as 500s instead of a clean 401/invalid-token response. Catch and wrap decode/parse errors into a KuraException with SECURITY_EXCEPTION (e.g., “Malformed JWT”).

Suggested change
final JsonObject header = parseJsonObject(parts[0]);
final JsonObject payload = parseJsonObject(parts[1]);
final String algorithm = payloadValue(header, "alg");
if (!JWT_ALG.equals(algorithm)) {
throw new KuraException(KuraErrorCode.SECURITY_EXCEPTION, "Unsupported JWT algorithm");
}
final String kid = payloadValue(header, "kid");
final String signingInput = parts[0] + "." + parts[1];
verifySignature(signingInput, parts[2], getVerificationPublicKey(kid));
validateClaims(payload);
return new DecodedJwt(payload);
try {
final JsonObject header = parseJsonObject(parts[0]);
final JsonObject payload = parseJsonObject(parts[1]);
final String algorithm = payloadValue(header, "alg");
if (!JWT_ALG.equals(algorithm)) {
throw new KuraException(KuraErrorCode.SECURITY_EXCEPTION, "Unsupported JWT algorithm");
}
final String kid = payloadValue(header, "kid");
final String signingInput = parts[0] + "." + parts[1];
verifySignature(signingInput, parts[2], getVerificationPublicKey(kid));
validateClaims(payload);
return new DecodedJwt(payload);
} catch (final RuntimeException e) {
throw new KuraException(KuraErrorCode.SECURITY_EXCEPTION, "Malformed JWT");
}

Copilot uses AI. Check for mistakes.
Comment on lines +1744 to +1758
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
switch (method.getName()) {
case "issueTokenPair":
return issueTokenPair((String) args[0]);
case "refreshTokenPair":
return refreshTokenPair((String) args[0]);
case "verifyAccessToken":
return verifyAccessToken((String) args[0]);
case "revokeTokenFamily":
revokeTokenFamily((String) args[0]);
return null;
default:
throw new UnsupportedOperationException("Unsupported method: " + method.getName());
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The InvocationHandler proxy used for the test IdentityTokenService only handles the service API methods. Calls to toString(), hashCode(), or equals() on the proxy will currently hit the default case and throw UnsupportedOperationException, which can break OSGi/service registry interactions or logging that implicitly calls these methods. Handle Object methods explicitly (or treat unknown methods as no-ops) so the proxy is safe to use as an OSGi service.

Copilot uses AI. Check for mistakes.
Comment on lines 114 to 117
} catch (final Exception e) {
stopMoquetteBroker();
initialized.set(false);
throw new RuntimeException(e);
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

In init(), the exception handler no longer calls stopMoquetteBroker(). If initialization fails after startMoquetteBroker() succeeds, the broker remains running and mqttBrokerPort stays allocated, which can cause flaky tests and resource leakage across runs. Consider stopping the broker (and resetting mqttBrokerPort if needed) in the failure path, as was previously done.

Copilot uses AI. Check for mistakes.
return new TokenAuthenticationResponseDTO(tokenPair, false);
} catch (final KuraException e) {
if (e.getCode() == KuraErrorCode.SECURITY_EXCEPTION) {
throw DefaultExceptionHandler.buildWebApplicationException(Status.UNAUTHORIZED, e.getMessage());
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

refreshTokenPair maps KuraErrorCode.SECURITY_EXCEPTION to HTTP 401 while passing e.getMessage() through to the client. Since IdentityTokenServiceImpl can produce detailed messages (e.g., missing claim names, signature/validation failures), this can unnecessarily leak validation details and create a token-oracle style distinction. Consider returning a fixed message like “Invalid refresh token” for all security exceptions while still logging the detailed cause server-side.

Suggested change
throw DefaultExceptionHandler.buildWebApplicationException(Status.UNAUTHORIZED, e.getMessage());
LoggerFactory.getLogger(SessionRestService.class).warn("Refresh token validation failed", e);
throw DefaultExceptionHandler.buildWebApplicationException(Status.UNAUTHORIZED,
"Invalid refresh token");

Copilot uses AI. Check for mistakes.
@salvatore-coppola salvatore-coppola force-pushed the feature/container-temporary-credentials-jwt branch from 9303a0d to 6a75649 Compare April 9, 2026 09:40
MMaiero and others added 5 commits April 10, 2026 18:26
Signed-off-by: MMaiero <matteo.maiero@eurotech.com>
…en error mapping

Disable the broken IdentityTokenServiceImpl ServiceFactory component
before registering the test IdentityTokenService to prevent the poisoned
0..1 SCR reference from blocking service binding. Reorder the
shouldSupportDisablingBuiltInJwtAuthentication test to login before
disabling JWT auth. Return 401 instead of 500 for SECURITY_EXCEPTION
in the token refresh endpoint.
- Use AtomicBoolean.compareAndSet for refresh token rotation to
  eliminate the race condition window between check and mark-as-used
- Track revoked token family expiry and prune in cleanup to prevent
  unbounded memory growth on long-running embedded devices
- Route all JWT claim reads through null-safe helpers to prevent
  NullPointerException on malformed tokens missing expected claims
- Remove dead isExpired guard from revocation check in refreshTokenPair
- Add audit logging to JWT logout endpoint matching the v1 pattern
- Create token files with restricted POSIX permissions atomically
  to close the TOCTOU window where tokens are briefly world-readable
- Derive token renewal interval from actual access token lifetime
  instead of using a hardcoded 60-second interval
@salvatore-coppola salvatore-coppola force-pushed the feature/container-temporary-credentials-jwt branch from 6a75649 to f819df2 Compare April 10, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants