Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2025 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Eurotech
******************************************************************************/
package org.eclipse.kura.identity;

import java.util.Set;

import org.eclipse.kura.KuraException;
import org.osgi.annotation.versioning.ProviderType;

/**
* A service interface that allows to manage temporary Kura identities for containers.
* Temporary identities are not persisted and are automatically removed when no longer needed.
*
* @noimplement This interface is not intended to be implemented by clients.
* @since 2.8.0
*/
@ProviderType
public interface TemporaryIdentityService {

/**
* Creates a temporary identity with the given name and permissions.
* The identity will not be persisted and will exist only in memory.
*
* @param identityName the name of the temporary identity to be created.
* @param permissions the set of permissions to be assigned to this temporary identity.
* @return a temporary authentication token that can be used to authenticate as this identity.
* @throws KuraException if a failure occurs in creating the temporary identity.
*/
public String createTemporaryIdentity(final String identityName, final Set<Permission> permissions) throws KuraException;

/**
* Deletes a temporary identity identified by its authentication token.
*
* @param token the authentication token of the temporary identity to be deleted.
* @return {@code true} if the temporary identity was deleted as part of the method call
* or {@code false} if the identity does not exist.
* @throws KuraException if a failure occurs in deleting the temporary identity.
*/
public boolean deleteTemporaryIdentity(final String token) throws KuraException;

/**
* Validates a temporary identity authentication token and returns the identity name.
*
* @param token the authentication token to validate.
* @return the identity name if the token is valid.
* @throws KuraException if the token is invalid or if a failure occurs during validation.
*/
public String validateTemporaryToken(final String token) throws KuraException;

/**
* Checks if the specified permission is currently assigned to the temporary identity
* identified by the given token.
*
* @param token the authentication token of the temporary identity.
* @param permission the permission to check.
* @throws KuraException if the provided permission is not currently assigned to
* the given temporary identity or if a failure occurs while performing the check.
*/
public void checkTemporaryPermission(final String token, final Permission permission) throws KuraException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Import-Package: org.eclipse.kura;version="[1.3,2.0)",
org.eclipse.kura.container.orchestration.listener;version="[1.0,1.1)",
org.eclipse.kura.container.signature;version="[1.0,2.0)",
org.eclipse.kura.crypto;version="[1.3,2.0)",
org.eclipse.kura.identity;version="[1.1,2.0)",
org.eclipse.kura.util.configuration;version="[1.0,2.0)",
org.osgi.service.component;version="1.4.0",
org.slf4j;version="1.7.25"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
<reference bind="setContainerOrchestrationService" cardinality="1..1" interface="org.eclipse.kura.container.orchestration.ContainerOrchestrationService" name="ContainerOrchestrationService" policy="static"/>
<reference bind="setContainerSignatureValidationService" cardinality="0..n" interface="org.eclipse.kura.container.signature.ContainerSignatureValidationService" name="ContainerSignatureValidationService" policy="dynamic" unbind="unsetContainerSignatureValidationService"/>
<reference bind="setConfigurationService" cardinality="1..1" interface="org.eclipse.kura.configuration.ConfigurationService" name="ConfigurationService" policy="static"/>
<reference bind="setTemporaryIdentityService" cardinality="0..1" interface="org.eclipse.kura.identity.TemporaryIdentityService" name="TemporaryIdentityService" policy="dynamic"/>
</scr:component>
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@
<AD id="container.restart.onfailure" name="Restart Container On Failure" type="Boolean" cardinality="1" required="true" default="false"
description="Automatically restart the container when it has failed. Default: false">
</AD>

<AD id="container.identity.enabled" name="Enable Identity Integration" type="Boolean" cardinality="1" required="true" default="false"
description="Enable integration with Kura Identity Service to provide temporary credentials to the container. Default: false">
</AD>

<AD id="container.permissions" name="Container Permissions"
description="Comma-separated list of permissions to grant to the container when identity integration is enabled. Example: rest.asset.read,rest.configuration.write"
type="String" cardinality="1" required="false" default="" />

</OCD>
<Designate pid="org.eclipse.kura.container.provider.ContainerInstance"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@

import static java.util.Objects.isNull;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
Expand All @@ -26,6 +28,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

import org.eclipse.kura.KuraException;
import org.eclipse.kura.configuration.ConfigurableComponent;
Expand All @@ -37,6 +40,8 @@
import org.eclipse.kura.container.orchestration.listener.ContainerOrchestrationServiceListener;
import org.eclipse.kura.container.signature.ContainerSignatureValidationService;
import org.eclipse.kura.container.signature.ValidationResult;
import org.eclipse.kura.identity.Permission;
import org.eclipse.kura.identity.TemporaryIdentityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -51,8 +56,10 @@ public class ContainerInstance implements ConfigurableComponent, ContainerOrches
private ContainerOrchestrationService containerOrchestrationService;
private Set<ContainerSignatureValidationService> availableContainerSignatureValidationService = new HashSet<>();
private ConfigurationService configurationService;
private TemporaryIdentityService temporaryIdentityService;
private State state = new Disabled(new ContainerInstanceOptions(Collections.emptyMap()));
private ContainerInstanceOptions currentOptions = null;
private String currentTemporaryToken = null;

public void setContainerOrchestrationService(final ContainerOrchestrationService containerOrchestrationService) {
this.containerOrchestrationService = containerOrchestrationService;
Expand All @@ -76,6 +83,10 @@ public synchronized void setConfigurationService(final ConfigurationService conf
this.configurationService = confService;
}

public synchronized void setTemporaryIdentityService(final TemporaryIdentityService temporaryIdentityService) {
this.temporaryIdentityService = temporaryIdentityService;
}

// ----------------------------------------------------------------
//
// Activation APIs
Expand Down Expand Up @@ -153,6 +164,7 @@ public void deactivate() {
logger.info("deactivate...");

updateState(State::onDisabled);
cleanupTemporaryIdentity();

this.executor.shutdown();
this.containerOrchestrationService.unregisterListener(this);
Expand Down Expand Up @@ -377,7 +389,8 @@ private void startMicroservice(final ContainerInstanceOptions options) {
int maxRetries = options.getMaxDownloadRetries();
int retryInterval = options.getRetryInterval();

final ContainerConfiguration containerConfiguration = options.getContainerConfiguration();
createTemporaryIdentityIfEnabled(options);
final ContainerConfiguration containerConfiguration = getContainerConfigurationWithCredentials(options);

int retries = 0;
while ((unlimitedRetries || retries < maxRetries) && !Thread.currentThread().isInterrupted()) {
Expand Down Expand Up @@ -435,6 +448,8 @@ private void deleteContainer() {
} catch (Exception e) {
logger.error("Error deleting microservice {}", this.options.getContainerName(), e);
}

cleanupTemporaryIdentity();
}

@Override
Expand Down Expand Up @@ -480,4 +495,72 @@ private void updateSnapshotWithSignatureDigest(Map<String, Object> properties) {
}
}

private void createTemporaryIdentityIfEnabled(final ContainerInstanceOptions options) {
if (options.isIdentityIntegrationEnabled() && this.temporaryIdentityService != null) {
try {
cleanupTemporaryIdentity();

final Set<Permission> permissions = options.getContainerPermissions().stream()
.map(Permission::new)
.collect(Collectors.toSet());

final String identityName = "container_" + options.getContainerName().replace("-", "_");
this.currentTemporaryToken = this.temporaryIdentityService.createTemporaryIdentity(identityName, permissions);

logger.info("Created temporary identity for container {} with {} permissions",
options.getContainerName(), permissions.size());

} catch (KuraException e) {
logger.error("Failed to create temporary identity for container {}", options.getContainerName(), e);
this.currentTemporaryToken = null;
}
}
}

private void cleanupTemporaryIdentity() {
if (this.currentTemporaryToken != null && this.temporaryIdentityService != null) {
try {
this.temporaryIdentityService.deleteTemporaryIdentity(this.currentTemporaryToken);
logger.info("Cleaned up temporary identity with token");
} catch (KuraException e) {
logger.warn("Failed to cleanup temporary identity", e);
} finally {
this.currentTemporaryToken = null;
}
}
}

private ContainerConfiguration getContainerConfigurationWithCredentials(final ContainerInstanceOptions options) {
ContainerConfiguration baseConfig = options.getContainerConfiguration();

if (options.isIdentityIntegrationEnabled() && this.currentTemporaryToken != null) {
final List<String> envVars = new ArrayList<>(baseConfig.getContainerEnvVars());
envVars.add("KURA_IDENTITY_TOKEN=" + this.currentTemporaryToken);
envVars.add("KURA_REST_BASE_URL=http://localhost:8080/services/rest");

return ContainerConfiguration.builder()
.setContainerName(baseConfig.getContainerName())
.setImageConfiguration(baseConfig.getImageConfiguration())
.setContainerPorts(baseConfig.getContainerPorts())
.setEnvVars(envVars)
.setVolumes(baseConfig.getContainerVolumes())
.setPrivilegedMode(baseConfig.isContainerPrivileged())
.setDeviceList(baseConfig.getContainerDevices())
.setFrameworkManaged(baseConfig.isFrameworkManaged())
.setLoggingType(baseConfig.getContainerLoggingType())
.setContainerNetowrkConfiguration(baseConfig.getContainerNetworkConfiguration())
Copy link

Copilot AI Sep 8, 2025

Choose a reason for hiding this comment

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

There is a typo in the method name. It should be "setContainerNetworkConfiguration" instead of "setContainerNetowrkConfiguration".

Suggested change
.setContainerNetowrkConfiguration(baseConfig.getContainerNetworkConfiguration())
.setContainerNetworkConfiguration(baseConfig.getContainerNetworkConfiguration())

Copilot uses AI. Check for mistakes.
.setLoggerParameters(baseConfig.getLoggerParameters())
.setEntryPoint(baseConfig.getEntryPoint())
.setRestartOnFailure(baseConfig.getRestartOnFailure())
.setMemory(baseConfig.getMemory())
.setCpus(baseConfig.getCpus())
.setGpus(baseConfig.getGpus())
.setRuntime(baseConfig.getRuntime())
.setEnforcementDigest(baseConfig.getEnforcementDigest())
.build();
}

return baseConfig;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public class ContainerInstanceOptions {
"container.signature.verify.transparency.log", true);
private static final Property<String> ENFORCEMENT_DIGEST = new Property<>("container.signature.enforcement.digest",
"");

private static final Property<Boolean> IDENTITY_INTEGRATION_ENABLED = new Property<>("container.identity.enabled", false);
private static final Property<String> CONTAINER_PERMISSIONS = new Property<>("container.permissions", "");

private boolean enabled;
private final String image;
Expand Down Expand Up @@ -107,6 +110,9 @@ public class ContainerInstanceOptions {
private final Boolean signatureVerifyTransparencyLog;

private final Optional<String> enforcementDigest;

private final boolean identityIntegrationEnabled;
private final List<String> containerPermissions;

public ContainerInstanceOptions(final Map<String, Object> properties) {
if (isNull(properties)) {
Expand Down Expand Up @@ -143,6 +149,8 @@ public ContainerInstanceOptions(final Map<String, Object> properties) {
this.signatureTrustAnchor = parseOptionalString(SIGNATURE_TRUST_ANCHOR.getOptional(properties));
this.signatureVerifyTransparencyLog = SIGNATURE_VERIFY_TLOG.get(properties);
this.enforcementDigest = parseOptionalString(ENFORCEMENT_DIGEST.getOptional(properties));
this.identityIntegrationEnabled = IDENTITY_INTEGRATION_ENABLED.get(properties);
this.containerPermissions = parseStringListSplitByComma(CONTAINER_PERMISSIONS.get(properties));
}

private Map<String, String> parseVolume(String volumeString) {
Expand Down Expand Up @@ -362,6 +370,14 @@ public Optional<String> getEnforcementDigest() {
return this.enforcementDigest;
}

public boolean isIdentityIntegrationEnabled() {
return this.identityIntegrationEnabled;
}

public List<String> getContainerPermissions() {
return this.containerPermissions;
}

private ImageConfiguration buildImageConfig() {
return new ImageConfiguration.ImageConfigurationBuilder().setImageName(this.image).setImageTag(this.imageTag)
.setImageDownloadTimeoutSeconds(this.imageDownloadTimeout)
Expand Down Expand Up @@ -438,8 +454,8 @@ private List<PortInternetProtocol> parsePortStringProtocol(String ports) {
public int hashCode() {
return Objects.hash(containerCpus, containerDevice, containerEntryPoint, containerEnv, containerGpus,
containerLoggerType, containerLoggingParameters, containerMemory, containerName,
containerNetworkingMode, containerPortProtocol, containerRuntime, containerVolumeString,
containerVolumes, enabled, enforcementDigest, externalPorts, image, imageDownloadTimeout, imageTag,
containerNetworkingMode, containerPermissions, containerPortProtocol, containerRuntime, containerVolumeString,
containerVolumes, enabled, enforcementDigest, externalPorts, identityIntegrationEnabled, image, imageDownloadTimeout, imageTag,
internalPorts, maxDownloadRetries, privilegedMode, registryPassword, registryURL, registryUsername,
restartOnFailure, retryInterval, signatureTrustAnchor, signatureVerifyTransparencyLog);
}
Expand All @@ -466,11 +482,14 @@ public boolean equals(Object obj) {
&& Objects.equals(containerMemory, other.containerMemory)
&& Objects.equals(containerName, other.containerName)
&& Objects.equals(containerNetworkingMode, other.containerNetworkingMode)
&& Objects.equals(containerPermissions, other.containerPermissions)
&& Objects.equals(containerPortProtocol, other.containerPortProtocol)
&& Objects.equals(containerVolumeString, other.containerVolumeString)
&& Objects.equals(containerVolumes, other.containerVolumes) && enabled == other.enabled
&& Objects.equals(enforcementDigest, other.enforcementDigest)
&& Objects.equals(externalPorts, other.externalPorts) && Objects.equals(image, other.image)
&& Objects.equals(externalPorts, other.externalPorts)
&& identityIntegrationEnabled == other.identityIntegrationEnabled
&& Objects.equals(image, other.image)
&& imageDownloadTimeout == other.imageDownloadTimeout && Objects.equals(imageTag, other.imageTag)
&& Objects.equals(internalPorts, other.internalPorts) && maxDownloadRetries == other.maxDownloadRetries
&& privilegedMode == other.privilegedMode && Objects.equals(registryPassword, other.registryPassword)
Expand Down
2 changes: 1 addition & 1 deletion kura/org.eclipse.kura.core.identity/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Import-Package: org.eclipse.kura;version="[1.7,2.0)",
org.eclipse.kura.audit;version="[1.0,2.0)",
org.eclipse.kura.configuration;version="[1.2,2.0)",
org.eclipse.kura.crypto;version="[1.3,2.0)",
org.eclipse.kura.identity;version="[1.1,1.2)",
org.eclipse.kura.identity;version="[1.1,2.0)",
org.eclipse.kura.identity.configuration.extension;version="[1.0,2.0)",
org.eclipse.kura.util.useradmin;version="[1.1,2.0)",
org.eclipse.kura.util.validation;version="[1.0,2.0)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<implementation class="org.eclipse.kura.core.identity.IdentityServiceImpl"/>
<service>
<provide interface="org.eclipse.kura.identity.IdentityService"/>
<provide interface="org.eclipse.kura.identity.TemporaryIdentityService"/>
</service>
<reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
<reference bind="setIdentityConfigurationExtension" cardinality="0..n" interface="org.eclipse.kura.identity.configuration.extension.IdentityConfigurationExtension" name="IdentityConfigurationExtension" policy="dynamic" unbind="unsetIdentityConfigurationExtension"/>
Expand Down
Loading
Loading