From 44b3cc0ac1545816ab66e881e953e0d77f58195e Mon Sep 17 00:00:00 2001 From: Jeremy Bernard Date: Mon, 1 Dec 2025 15:23:02 +0100 Subject: [PATCH] feat: generate and post TDX session to session storage --- .../authorization/AuthorizationService.java | 2 +- .../java/com/iexec/sms/ssl/SslConfig.java | 21 +-- .../TeeWorkerInternalConfiguration.java | 3 + .../sms/tee/session/TeeSessionService.java | 10 +- .../base/SecretSessionBaseService.java | 28 ++-- .../session/tdx/TdxSessionHandlerService.java | 37 ++++- .../session/tdx/TdxSessionMakerService.java | 64 +++++++++ .../tee/session/tdx/storage/TdxSession.java | 25 ++++ .../storage/TdxSessionStorageApiClient.java | 24 ++++ .../TdxSessionStorageConfiguration.java | 30 ++++ .../TeeWorkerInternalConfigurationTests.java | 54 ++++---- .../sms/tee/session/TeeSessionTestUtils.java | 2 + .../base/SecretSessionBaseServiceTests.java | 35 ++--- .../tdx/TdxSessionHandlerServiceTests.java | 70 +++++++++- .../tdx/TdxSessionMakerServiceTests.java | 128 ++++++++++++++++++ 15 files changed, 450 insertions(+), 83 deletions(-) create mode 100644 src/main/java/com/iexec/sms/tee/session/tdx/TdxSessionMakerService.java create mode 100644 src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSession.java create mode 100644 src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSessionStorageApiClient.java create mode 100644 src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSessionStorageConfiguration.java create mode 100644 src/test/java/com/iexec/sms/tee/session/tdx/TdxSessionMakerServiceTests.java diff --git a/src/main/java/com/iexec/sms/authorization/AuthorizationService.java b/src/main/java/com/iexec/sms/authorization/AuthorizationService.java index 81c3c470..c01afc76 100644 --- a/src/main/java/com/iexec/sms/authorization/AuthorizationService.java +++ b/src/main/java/com/iexec/sms/authorization/AuthorizationService.java @@ -78,7 +78,7 @@ public Optional isAuthorizedOnExecutionWithDetailedIssue(fin return Optional.of(GET_CHAIN_DEAL_FAILED); } - final boolean isTeeTaskOnchain = TeeUtils.isTeeTag(chainDeal.getTag()); + final boolean isTeeTaskOnchain = TeeUtils.getTeeFramework(chainDeal.getTag()) != null; if (!isTeeTaskOnchain) { log.error("Could not match onchain task type [isTeeTaskOnchain:{}, chainTaskId:{}]", isTeeTaskOnchain, chainTaskId); diff --git a/src/main/java/com/iexec/sms/ssl/SslConfig.java b/src/main/java/com/iexec/sms/ssl/SslConfig.java index e3359d84..4d49048b 100644 --- a/src/main/java/com/iexec/sms/ssl/SslConfig.java +++ b/src/main/java/com/iexec/sms/ssl/SslConfig.java @@ -20,7 +20,8 @@ import com.iexec.sms.tee.ConditionalOnTeeFramework; import lombok.Value; import lombok.extern.slf4j.Slf4j; -import org.apache.hc.core5.ssl.SSLContexts; +import org.apache.commons.lang3.StringUtils; +import org.apache.hc.core5.ssl.SSLContextBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import javax.net.ssl.SSLContext; @@ -35,7 +36,7 @@ @Slf4j @Value @ConfigurationProperties(prefix = "tee.ssl") -@ConditionalOnTeeFramework(frameworks = TeeFramework.SCONE) +@ConditionalOnTeeFramework(frameworks = {TeeFramework.SCONE, TeeFramework.TDX}) public class SslConfig { String keystore; @@ -48,14 +49,14 @@ public class SslConfig { */ public SSLContext getFreshSslContext() { try { - return SSLContexts.custom() - .setKeyStoreType(keystoreType) - .loadKeyMaterial(new File(keystore), - keystorePassword, - keystorePassword, - (aliases, socket) -> keyAlias) - .loadTrustMaterial(null, (chain, authType) -> true) //TODO: Add CAS certificate to truststore - .build(); + final SSLContextBuilder sslContextBuilder = SSLContextBuilder.create(); + if (!StringUtils.isEmpty(keystore)) { + sslContextBuilder.setKeyStoreType(keystoreType).loadKeyMaterial( + new File(keystore), keystorePassword, keystorePassword, + ((aliases, sslParameters) -> keyAlias)); + } + sslContextBuilder.loadTrustMaterial(null, (chain, authType) -> true); //TODO: Add CAS certificate to truststore + return sslContextBuilder.build(); } catch (IOException | NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException | CertificateException | KeyManagementException e) { log.warn("Failed to create a fresh SSL context", e); diff --git a/src/main/java/com/iexec/sms/tee/config/TeeWorkerInternalConfiguration.java b/src/main/java/com/iexec/sms/tee/config/TeeWorkerInternalConfiguration.java index 966b32fd..29360ce3 100644 --- a/src/main/java/com/iexec/sms/tee/config/TeeWorkerInternalConfiguration.java +++ b/src/main/java/com/iexec/sms/tee/config/TeeWorkerInternalConfiguration.java @@ -65,6 +65,9 @@ public Map sconeServicesPropertiesMap( @ConditionalOnTeeFramework(frameworks = TeeFramework.TDX) public Map tdxServicesPropertiesMap( final TeeWorkerPipelineConfiguration pipelineConfig) { + if (pipelineConfig.getPipelines().size() != 1) { + throw new IllegalStateException("Only a singleton pipeline list is currently supported for TDX"); + } return pipelineConfig.getPipelines().stream() .map(pipeline -> new TdxServicesProperties( pipeline.version(), diff --git a/src/main/java/com/iexec/sms/tee/session/TeeSessionService.java b/src/main/java/com/iexec/sms/tee/session/TeeSessionService.java index 352ec965..6fd3a0fc 100644 --- a/src/main/java/com/iexec/sms/tee/session/TeeSessionService.java +++ b/src/main/java/com/iexec/sms/tee/session/TeeSessionService.java @@ -62,16 +62,19 @@ public TeeSessionGenerationResponse generateTeeSession( } final TeeEnclaveConfiguration teeEnclaveConfiguration = taskDescription.getAppEnclaveConfiguration(); - if (teeEnclaveConfiguration == null) { + if (taskDescription.requiresSgx() && teeEnclaveConfiguration == null) { throw new TeeSessionGenerationException( APP_COMPUTE_NO_ENCLAVE_CONFIG, String.format("TEE enclave configuration can't be null [taskId:%s]", taskId)); } final TeeServicesProperties teeServicesProperties; - try { - teeServicesProperties = resolveTeeServiceProperties(teeEnclaveConfiguration.getVersion()); + if (taskDescription.requiresSgx()) { + teeServicesProperties = resolveTeeServiceProperties(teeEnclaveConfiguration.getVersion()); + } else { + teeServicesProperties = teeServicesPropertiesMap.values().stream().findFirst().orElseThrow(); + } } catch (NoSuchElementException e) { // TODO Add appropriate error type throw new TeeSessionGenerationException( @@ -94,7 +97,6 @@ public TeeSessionGenerationResponse generateTeeSession( String.format("TEE framework can't be null [taskId:%s]", taskId)); } - // /!\ TODO clean expired tasks sessions final String secretProvisioningUrl = teeSessionHandler.buildAndPostSession(request); return new TeeSessionGenerationResponse(sessionId, secretProvisioningUrl); } diff --git a/src/main/java/com/iexec/sms/tee/session/base/SecretSessionBaseService.java b/src/main/java/com/iexec/sms/tee/session/base/SecretSessionBaseService.java index fb27e630..89511d92 100644 --- a/src/main/java/com/iexec/sms/tee/session/base/SecretSessionBaseService.java +++ b/src/main/java/com/iexec/sms/tee/session/base/SecretSessionBaseService.java @@ -308,20 +308,22 @@ SecretEnclaveBase getAppTokens(final TeeSessionRequest request) throws TeeSessio final TaskDescription taskDescription = request.getTaskDescription(); final Map tokens = new HashMap<>(); - final TeeEnclaveConfiguration enclaveConfig = taskDescription.getAppEnclaveConfiguration(); - if (enclaveConfig == null) { - throw new TeeSessionGenerationException( - APP_COMPUTE_NO_ENCLAVE_CONFIG, - "Enclave configuration must not be null"); - } - if (!enclaveConfig.getValidator().isValid()) { - throw new TeeSessionGenerationException( - APP_COMPUTE_INVALID_ENCLAVE_CONFIG, - "Invalid enclave configuration: " + - enclaveConfig.getValidator().validate().toString()); - } + if (taskDescription.requiresSgx()) { + final TeeEnclaveConfiguration enclaveConfig = taskDescription.getAppEnclaveConfiguration(); + if (enclaveConfig == null) { + throw new TeeSessionGenerationException( + APP_COMPUTE_NO_ENCLAVE_CONFIG, + "Enclave configuration must not be null"); + } + if (!enclaveConfig.getValidator().isValid()) { + throw new TeeSessionGenerationException( + APP_COMPUTE_INVALID_ENCLAVE_CONFIG, + "Invalid enclave configuration: " + + enclaveConfig.getValidator().validate().toString()); + } - enclaveBase.mrenclave(enclaveConfig.getFingerprint()); + enclaveBase.mrenclave(enclaveConfig.getFingerprint()); + } final Map computeSecrets = getApplicationComputeSecrets(taskDescription); tokens.putAll(computeSecrets); diff --git a/src/main/java/com/iexec/sms/tee/session/tdx/TdxSessionHandlerService.java b/src/main/java/com/iexec/sms/tee/session/tdx/TdxSessionHandlerService.java index 188b10c1..f3814a24 100644 --- a/src/main/java/com/iexec/sms/tee/session/tdx/TdxSessionHandlerService.java +++ b/src/main/java/com/iexec/sms/tee/session/tdx/TdxSessionHandlerService.java @@ -16,17 +16,40 @@ package com.iexec.sms.tee.session.tdx; +import com.iexec.common.utils.FeignBuilder; import com.iexec.commons.poco.tee.TeeFramework; import com.iexec.sms.api.TeeSessionGenerationError; +import com.iexec.sms.ssl.SslConfig; import com.iexec.sms.tee.ConditionalOnTeeFramework; import com.iexec.sms.tee.session.generic.TeeSessionGenerationException; import com.iexec.sms.tee.session.generic.TeeSessionHandler; import com.iexec.sms.tee.session.generic.TeeSessionRequest; +import com.iexec.sms.tee.session.tdx.storage.TdxSession; +import com.iexec.sms.tee.session.tdx.storage.TdxSessionStorageApiClient; +import com.iexec.sms.tee.session.tdx.storage.TdxSessionStorageConfiguration; +import feign.Client; +import feign.Logger; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; import org.springframework.stereotype.Service; @Service @ConditionalOnTeeFramework(frameworks = TeeFramework.TDX) public class TdxSessionHandlerService implements TeeSessionHandler { + private final TdxSessionMakerService sessionService; + private final TdxSessionStorageConfiguration storageConfiguration; + private final TdxSessionStorageApiClient storageClient; + + public TdxSessionHandlerService( + final SslConfig sslConfig, + final TdxSessionMakerService sessionService, + final TdxSessionStorageConfiguration storageConfiguration) { + this.sessionService = sessionService; + this.storageConfiguration = storageConfiguration; + this.storageClient = FeignBuilder.createBuilder(Logger.Level.FULL) + .client(new Client.Default(sslConfig.getFreshSslContext().getSocketFactory(), NoopHostnameVerifier.INSTANCE)) + .target(TdxSessionStorageApiClient.class, storageConfiguration.getPostUrl()); + } + /** * Build and post secret session on secret provisioning service. * @@ -36,9 +59,15 @@ public class TdxSessionHandlerService implements TeeSessionHandler { */ @Override public String buildAndPostSession(final TeeSessionRequest request) throws TeeSessionGenerationException { - throw new TeeSessionGenerationException( - TeeSessionGenerationError.SECURE_SESSION_STORAGE_CALL_FAILED, - "Not implemented yet" - ); + final TdxSession session = sessionService.generateSession(request); + + try { + storageClient.postSession(session); + return storageConfiguration.getRemoteAttestationUrl(); + } catch (Exception e) { + throw new TeeSessionGenerationException( + TeeSessionGenerationError.SECURE_SESSION_STORAGE_CALL_FAILED, + "Failed to post session: " + e.getMessage()); + } } } diff --git a/src/main/java/com/iexec/sms/tee/session/tdx/TdxSessionMakerService.java b/src/main/java/com/iexec/sms/tee/session/tdx/TdxSessionMakerService.java new file mode 100644 index 00000000..d8a1f2f9 --- /dev/null +++ b/src/main/java/com/iexec/sms/tee/session/tdx/TdxSessionMakerService.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 IEXEC BLOCKCHAIN TECH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.iexec.sms.tee.session.tdx; + +import com.iexec.commons.poco.tee.TeeFramework; +import com.iexec.sms.api.config.TeeAppProperties; +import com.iexec.sms.tee.ConditionalOnTeeFramework; +import com.iexec.sms.tee.session.base.SecretEnclaveBase; +import com.iexec.sms.tee.session.base.SecretSessionBase; +import com.iexec.sms.tee.session.base.SecretSessionBaseService; +import com.iexec.sms.tee.session.generic.TeeSessionGenerationException; +import com.iexec.sms.tee.session.generic.TeeSessionRequest; +import com.iexec.sms.tee.session.tdx.storage.TdxSession; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +@ConditionalOnTeeFramework(frameworks = TeeFramework.TDX) +public class TdxSessionMakerService { + public static final String TDX_SESSION_VERSION = "0.1.0"; + private final SecretSessionBaseService secretSessionBaseService; + + public TdxSessionMakerService(final SecretSessionBaseService secretSessionBaseService) { + this.secretSessionBaseService = secretSessionBaseService; + } + + public TdxSession generateSession(final TeeSessionRequest request) throws TeeSessionGenerationException { + final SecretSessionBase baseSession = secretSessionBaseService.getSecretsTokens(request); + final List tdxEnclaves = new ArrayList<>(); + if (baseSession.getPreCompute() != null) { + tdxEnclaves.add(toTdxEnclave(baseSession.getPreCompute(), request.getTeeServicesProperties().getPreComputeProperties())); + } + // FIXME fingerprint should be dapp checksum from TaskDescription after commons-poco update + tdxEnclaves.add(toTdxEnclave(baseSession.getAppCompute(), request.getTaskDescription().getAppUri(), "")); + tdxEnclaves.add(toTdxEnclave(baseSession.getPostCompute(), request.getTeeServicesProperties().getPostComputeProperties())); + return new TdxSession(request.getSessionId(), TDX_SESSION_VERSION, List.copyOf(tdxEnclaves)); + } + + private TdxSession.Service toTdxEnclave(final SecretEnclaveBase enclaveBase, final TeeAppProperties teeAppProperties) { + return toTdxEnclave(enclaveBase, teeAppProperties.getImage(), teeAppProperties.getFingerprint()); + } + + private TdxSession.Service toTdxEnclave(final SecretEnclaveBase enclaveBase, final String image_name, final String fingerprint) { + return new TdxSession.Service( + enclaveBase.getName(), image_name, fingerprint, enclaveBase.getEnvironment() + ); + } +} diff --git a/src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSession.java b/src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSession.java new file mode 100644 index 00000000..dfffb5c8 --- /dev/null +++ b/src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSession.java @@ -0,0 +1,25 @@ +/* + * Copyright 2025 IEXEC BLOCKCHAIN TECH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.iexec.sms.tee.session.tdx.storage; + +import java.util.List; +import java.util.Map; + +public record TdxSession(String name, String version, List services) { + public record Service(String name, String image_name, String fingerprint, Map environment) { + } +} diff --git a/src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSessionStorageApiClient.java b/src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSessionStorageApiClient.java new file mode 100644 index 00000000..61c0221f --- /dev/null +++ b/src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSessionStorageApiClient.java @@ -0,0 +1,24 @@ +/* + * Copyright 2025 IEXEC BLOCKCHAIN TECH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.iexec.sms.tee.session.tdx.storage; + +import feign.RequestLine; + +public interface TdxSessionStorageApiClient { + @RequestLine("POST /session") + String postSession(TdxSession tdxSession); +} diff --git a/src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSessionStorageConfiguration.java b/src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSessionStorageConfiguration.java new file mode 100644 index 00000000..94b0ba89 --- /dev/null +++ b/src/main/java/com/iexec/sms/tee/session/tdx/storage/TdxSessionStorageConfiguration.java @@ -0,0 +1,30 @@ +/* + * Copyright 2025 IEXEC BLOCKCHAIN TECH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.iexec.sms.tee.session.tdx.storage; + +import com.iexec.commons.poco.tee.TeeFramework; +import com.iexec.sms.tee.ConditionalOnTeeFramework; +import lombok.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Value +@ConfigurationProperties(prefix = "tee.secret-provisioner") +@ConditionalOnTeeFramework(frameworks = TeeFramework.TDX) +public class TdxSessionStorageConfiguration { + String postUrl; + String remoteAttestationUrl; +} diff --git a/src/test/java/com/iexec/sms/tee/config/TeeWorkerInternalConfigurationTests.java b/src/test/java/com/iexec/sms/tee/config/TeeWorkerInternalConfigurationTests.java index e85f7b8b..ecb69094 100644 --- a/src/test/java/com/iexec/sms/tee/config/TeeWorkerInternalConfigurationTests.java +++ b/src/test/java/com/iexec/sms/tee/config/TeeWorkerInternalConfigurationTests.java @@ -17,14 +17,15 @@ package com.iexec.sms.tee.config; import com.iexec.sms.api.config.*; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.util.unit.DataSize; +import java.util.ArrayList; import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; class TeeWorkerInternalConfigurationTests { private static final String PRE_IMAGE = "preComputeImage"; @@ -34,7 +35,8 @@ class TeeWorkerInternalConfigurationTests { private static final String POST_FINGERPRINT = "postComputeFingerprint"; private static final String POST_ENTRYPOINT = "postComputeEntrypoint"; private static final String LAS_IMAGE = "lasImage"; - private static final String VERSION = "v5"; + private static final String VERSION_5 = "v5"; + private static final String VERSION_6 = "v6"; private static final long HEAP_SIZE_B = 3221225472L; private final TeeAppProperties preComputeProperties = TeeAppProperties.builder() @@ -50,22 +52,19 @@ class TeeWorkerInternalConfigurationTests { .heapSizeInBytes(HEAP_SIZE_B) .build(); - private TeeWorkerInternalConfiguration teeWorkerInternalConfiguration; - private TeeWorkerPipelineConfiguration pipelineConfig; + private final TeeWorkerInternalConfiguration teeWorkerInternalConfiguration = new TeeWorkerInternalConfiguration(); - @BeforeEach - void setUp() { - teeWorkerInternalConfiguration = new TeeWorkerInternalConfiguration(); + private TeeWorkerPipelineConfiguration getPipelineConfig(final boolean multiplePipelines) { + final List pipelines = new ArrayList<>(); + pipelines.add(getPipeline(VERSION_5)); + if (multiplePipelines) { + pipelines.add(getPipeline(VERSION_6)); + } - final TeeWorkerPipelineConfiguration.Pipeline pipeline = getPipeline(VERSION); - final TeeWorkerPipelineConfiguration.Pipeline additionalPipeline = getPipeline("v6"); - - pipelineConfig = new TeeWorkerPipelineConfiguration( - List.of(pipeline, additionalPipeline) - ); + return new TeeWorkerPipelineConfiguration(List.copyOf(pipelines)); } - private static TeeWorkerPipelineConfiguration.Pipeline getPipeline(final String version) { + private TeeWorkerPipelineConfiguration.Pipeline getPipeline(final String version) { final TeeWorkerPipelineConfiguration.StageConfig validPreComputeStageConfig = new TeeWorkerPipelineConfiguration.StageConfig( PRE_IMAGE, PRE_FINGERPRINT, @@ -90,43 +89,50 @@ private static TeeWorkerPipelineConfiguration.Pipeline getPipeline(final String @Test void shouldBuildGramineServicesPropertiesMap() { final Map multiPropertiesMap = - teeWorkerInternalConfiguration.gramineServicesPropertiesMap(pipelineConfig); + teeWorkerInternalConfiguration.gramineServicesPropertiesMap(getPipelineConfig(true)); assertThat(multiPropertiesMap).isNotNull() .extracting(Map::size) .isEqualTo(2); - assertThat(multiPropertiesMap.get(VERSION)) + assertThat(multiPropertiesMap.get(VERSION_5)) .usingRecursiveComparison() - .isEqualTo(new GramineServicesProperties(VERSION, preComputeProperties, postComputeProperties)); + .isEqualTo(new GramineServicesProperties(VERSION_5, preComputeProperties, postComputeProperties)); } @Test void shouldBuildSconeServicesPropertiesMap() { final Map multiPropertiesMap = - teeWorkerInternalConfiguration.sconeServicesPropertiesMap(pipelineConfig, LAS_IMAGE); + teeWorkerInternalConfiguration.sconeServicesPropertiesMap(getPipelineConfig(true), LAS_IMAGE); assertThat(multiPropertiesMap).isNotNull() .extracting(Map::size) .isEqualTo(2); - assertThat(multiPropertiesMap.get(VERSION)) + assertThat(multiPropertiesMap.get(VERSION_5)) .usingRecursiveComparison() - .isEqualTo(new SconeServicesProperties(VERSION, preComputeProperties, postComputeProperties, LAS_IMAGE)); + .isEqualTo(new SconeServicesProperties(VERSION_5, preComputeProperties, postComputeProperties, LAS_IMAGE)); } @Test void shouldBuildTdxServicesPropertiesMap() { final Map multiPropertiesMap = - teeWorkerInternalConfiguration.tdxServicesPropertiesMap(pipelineConfig); + teeWorkerInternalConfiguration.tdxServicesPropertiesMap(getPipelineConfig(false)); assertThat(multiPropertiesMap).isNotNull() .extracting(Map::size) - .isEqualTo(2); + .isEqualTo(1); - assertThat(multiPropertiesMap.get(VERSION)) + assertThat(multiPropertiesMap.get(VERSION_5)) .usingRecursiveComparison() - .isEqualTo(new TdxServicesProperties(VERSION, preComputeProperties, postComputeProperties)); + .isEqualTo(new TdxServicesProperties(VERSION_5, preComputeProperties, postComputeProperties)); } + @Test + void shouldNotBuildTdxServicesPropertiesMapWhenMultiplePipelines() { + final TeeWorkerPipelineConfiguration pipelineConfig = getPipelineConfig(true); + assertThatThrownBy(() -> teeWorkerInternalConfiguration.tdxServicesPropertiesMap(pipelineConfig)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Only a singleton pipeline list is currently supported for TDX"); + } } diff --git a/src/test/java/com/iexec/sms/tee/session/TeeSessionTestUtils.java b/src/test/java/com/iexec/sms/tee/session/TeeSessionTestUtils.java index 10d191f5..9b10cd2b 100644 --- a/src/test/java/com/iexec/sms/tee/session/TeeSessionTestUtils.java +++ b/src/test/java/com/iexec/sms/tee/session/TeeSessionTestUtils.java @@ -21,6 +21,7 @@ import com.iexec.commons.poco.order.OrderTag; import com.iexec.commons.poco.task.TaskDescription; import com.iexec.commons.poco.tee.TeeEnclaveConfiguration; +import com.iexec.commons.poco.tee.TeeUtils; import com.iexec.commons.poco.utils.BytesUtils; import com.iexec.sms.api.config.SconeServicesProperties; import com.iexec.sms.api.config.TeeAppProperties; @@ -152,6 +153,7 @@ public static TaskDescription.TaskDescriptionBuilder createTaskDescription(final .appEnclaveConfiguration(enclaveConfig) .datasetAddress(BytesUtils.EMPTY_ADDRESS) .tag(OrderTag.TEE_SCONE.getValue()) + .teeFramework(TeeUtils.getTeeFramework(OrderTag.TEE_SCONE.getValue())) .requester(requesterAddress) .beneficiary(beneficiaryAddress) .dealParams(dealParams) diff --git a/src/test/java/com/iexec/sms/tee/session/base/SecretSessionBaseServiceTests.java b/src/test/java/com/iexec/sms/tee/session/base/SecretSessionBaseServiceTests.java index 3676da39..0e10a343 100644 --- a/src/test/java/com/iexec/sms/tee/session/base/SecretSessionBaseServiceTests.java +++ b/src/test/java/com/iexec/sms/tee/session/base/SecretSessionBaseServiceTests.java @@ -47,6 +47,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; @@ -467,9 +468,13 @@ void shouldGetAppComputeBulkProcessingTokens() throws TeeSessionGenerationExcept )); } - @Test - void shouldGetAppTokensForAdvancedTaskDescription() throws TeeSessionGenerationException { - final TeeSessionRequest request = createSessionRequest(createTaskDescriptionWithDataset(createDealParams().build(), enclaveConfig).build()); + @ParameterizedTest + @EnumSource(value = TeeFramework.class, names = {"SCONE", "TDX"}) + void shouldGetAppTokensForAdvancedTaskDescription(final TeeFramework teeFramework) throws TeeSessionGenerationException { + final TaskDescription taskDescription = createTaskDescriptionWithDataset(createDealParams().build(), enclaveConfig) + .teeFramework(teeFramework) + .build(); + final TeeSessionRequest request = createSessionRequest(taskDescription); final String appAddress = request.getTaskDescription().getAppAddress(); final String requesterAddress = request.getTaskDescription().getRequester(); @@ -482,7 +487,7 @@ void shouldGetAppTokensForAdvancedTaskDescription() throws TeeSessionGenerationE final SecretEnclaveBase enclaveBase = teeSecretsService.getAppTokens(request); assertThat(enclaveBase.getName()).isEqualTo("app"); - assertThat(enclaveBase.getMrenclave()).isEqualTo(APP_FINGERPRINT); + assertThat(enclaveBase.getMrenclave()).isEqualTo(teeFramework != TeeFramework.TDX ? APP_FINGERPRINT : null); final Map expectedTokens = Map.ofEntries( Map.entry("IEXEC_DEAL_ID", DEAL_ID), Map.entry("IEXEC_TASK_INDEX", "0"), @@ -512,29 +517,13 @@ void shouldGetAppTokensForAdvancedTaskDescription() throws TeeSessionGenerationE @Test void shouldGetTokensWithEmptyAppComputeSecretWhenSecretsDoNotExist() throws TeeSessionGenerationException { - final String appAddress = "0xapp"; - final String requesterAddress = "0xrequester"; final DealParams dealParams = createDealParams() .iexecSecrets(Map.of()) .build(); - final TaskDescription taskDescription = TaskDescription.builder() - .chainDealId(DEAL_ID) - .chainTaskId(TASK_ID) - .appUri(APP_URI) - .appAddress(appAddress) - .appEnclaveConfiguration(enclaveConfig) - .datasetAddress(DATASET_ADDRESS) - .datasetUri(DATASET_URL) - .datasetChecksum(DATASET_CHECKSUM) - .requester(requesterAddress) - .dealParams(dealParams) - .botSize(1) - .botFirstIndex(0) - .botIndex(0) - .build(); + final TaskDescription taskDescription = createTaskDescriptionWithDataset(dealParams, enclaveConfig).build(); final TeeSessionRequest request = createSessionRequest(taskDescription); - final TeeTaskComputeSecret applicationSecret = getApplicationDeveloperSecret(appAddress); + final TeeTaskComputeSecret applicationSecret = getApplicationDeveloperSecret(taskDescription.getAppAddress()); when(teeTaskComputeSecretService.getSecretsForTeeSession(List.of(applicationSecret.getHeader()))) .thenReturn(List.of()); @@ -568,7 +557,7 @@ void shouldFailToGetAppTokensSinceNoEnclaveConfig() { .sessionId(SESSION_ID) .workerAddress(WORKER_ADDRESS) .enclaveChallenge(ENCLAVE_CHALLENGE) - .taskDescription(TaskDescription.builder().build()) + .taskDescription(TaskDescription.builder().teeFramework(TeeFramework.SCONE).build()) .build(); final TeeSessionGenerationException exception = assertThrows(TeeSessionGenerationException.class, () -> teeSecretsService.getAppTokens(request)); diff --git a/src/test/java/com/iexec/sms/tee/session/tdx/TdxSessionHandlerServiceTests.java b/src/test/java/com/iexec/sms/tee/session/tdx/TdxSessionHandlerServiceTests.java index e59a5795..225a60b4 100644 --- a/src/test/java/com/iexec/sms/tee/session/tdx/TdxSessionHandlerServiceTests.java +++ b/src/test/java/com/iexec/sms/tee/session/tdx/TdxSessionHandlerServiceTests.java @@ -16,21 +16,83 @@ package com.iexec.sms.tee.session.tdx; +import com.iexec.commons.poco.task.TaskDescription; import com.iexec.sms.api.TeeSessionGenerationError; +import com.iexec.sms.ssl.SslConfig; import com.iexec.sms.tee.session.generic.TeeSessionGenerationException; import com.iexec.sms.tee.session.generic.TeeSessionRequest; +import com.iexec.sms.tee.session.tdx.storage.TdxSession; +import com.iexec.sms.tee.session.tdx.storage.TdxSessionStorageApiClient; +import com.iexec.sms.tee.session.tdx.storage.TdxSessionStorageConfiguration; +import feign.FeignException; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import javax.net.ssl.SSLContext; +import java.security.NoSuchAlgorithmException; + +import static com.iexec.sms.tee.session.TeeSessionTestUtils.createSessionRequest; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; +@ExtendWith(MockitoExtension.class) class TdxSessionHandlerServiceTests { - private final TdxSessionHandlerService sessionHandlerService = new TdxSessionHandlerService(); + private static final String STORAGE_SESSION_URL = "storageSessionUrl"; + @Mock + private SslConfig sslConfig; + @Mock + private TdxSessionMakerService sessionService; + @Mock + private TdxSessionStorageConfiguration storageConfiguration; + private TdxSessionHandlerService sessionHandlerService; + + @Mock + private TdxSessionStorageApiClient storageClient; + + private void setupMockService() throws NoSuchAlgorithmException { + when(sslConfig.getFreshSslContext()).thenReturn(SSLContext.getDefault()); + when(storageConfiguration.getPostUrl()).thenReturn("http://session-storage"); + sessionHandlerService = new TdxSessionHandlerService(sslConfig, sessionService, storageConfiguration); + ReflectionTestUtils.setField(sessionHandlerService, "storageClient", storageClient); + } + + @Test + void shouldBuildAndPostSession() throws TeeSessionGenerationException, NoSuchAlgorithmException { + setupMockService(); + final TaskDescription taskDescription = TaskDescription.builder().build(); + final TeeSessionRequest request = createSessionRequest(taskDescription); + final TdxSession tdxSession = mock(TdxSession.class); + when(sessionService.generateSession(request)).thenReturn(tdxSession); + when(storageConfiguration.getRemoteAttestationUrl()).thenReturn(STORAGE_SESSION_URL); + assertThat(sessionHandlerService.buildAndPostSession(request)).isEqualTo(STORAGE_SESSION_URL); + verify(storageClient).postSession(tdxSession); + } + + @Test + void shouldNotBuildAndPostSessionSinceBuildSessionFailed() throws TeeSessionGenerationException, NoSuchAlgorithmException { + setupMockService(); + final TeeSessionRequest request = TeeSessionRequest.builder().build(); + TeeSessionGenerationException teeSessionGenerationException = new TeeSessionGenerationException( + TeeSessionGenerationError.SECURE_SESSION_GENERATION_FAILED, "some error"); + when(sessionService.generateSession(request)).thenThrow(teeSessionGenerationException); + assertThatThrownBy(() -> sessionHandlerService.buildAndPostSession(request)) + .isInstanceOf(TeeSessionGenerationException.class) + .hasFieldOrPropertyWithValue("error", TeeSessionGenerationError.SECURE_SESSION_GENERATION_FAILED); + } @Test - void shouldThrow() { - assertThatThrownBy(() -> sessionHandlerService.buildAndPostSession(TeeSessionRequest.builder().build())) + void shouldNotBuildAndPostSessionSincePostSessionFailed() throws TeeSessionGenerationException, NoSuchAlgorithmException { + setupMockService(); + final TeeSessionRequest request = TeeSessionRequest.builder().build(); + final TdxSession tdxSession = mock(TdxSession.class); + when(sessionService.generateSession(request)).thenReturn(tdxSession); + when(storageClient.postSession(tdxSession)).thenThrow(FeignException.class); + assertThatThrownBy(() -> sessionHandlerService.buildAndPostSession(request)) .isInstanceOf(TeeSessionGenerationException.class) - .hasMessage("Not implemented yet") .hasFieldOrPropertyWithValue("error", TeeSessionGenerationError.SECURE_SESSION_STORAGE_CALL_FAILED); } } diff --git a/src/test/java/com/iexec/sms/tee/session/tdx/TdxSessionMakerServiceTests.java b/src/test/java/com/iexec/sms/tee/session/tdx/TdxSessionMakerServiceTests.java new file mode 100644 index 00000000..c29bf158 --- /dev/null +++ b/src/test/java/com/iexec/sms/tee/session/tdx/TdxSessionMakerServiceTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2025 IEXEC BLOCKCHAIN TECH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.iexec.sms.tee.session.tdx; + +import com.iexec.commons.poco.tee.TeeEnclaveConfiguration; +import com.iexec.commons.poco.tee.TeeFramework; +import com.iexec.sms.api.config.TdxServicesProperties; +import com.iexec.sms.api.config.TeeAppProperties; +import com.iexec.sms.tee.session.base.SecretEnclaveBase; +import com.iexec.sms.tee.session.base.SecretSessionBase; +import com.iexec.sms.tee.session.base.SecretSessionBaseService; +import com.iexec.sms.tee.session.generic.TeeSessionGenerationException; +import com.iexec.sms.tee.session.generic.TeeSessionRequest; +import com.iexec.sms.tee.session.tdx.storage.TdxSession; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Map; + +import static com.iexec.sms.tee.session.TeeSessionTestUtils.*; +import static com.iexec.sms.tee.session.tdx.TdxSessionMakerService.TDX_SESSION_VERSION; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TdxSessionMakerServiceTests { + private static final String TDX_FRAMEWORK_VERSION = "v1"; + + @Mock + private SecretSessionBaseService secretSessionBaseService; + @InjectMocks + private TdxSessionMakerService tdxSessionMakerService; + + private final TeeAppProperties preComputeProperties = TeeAppProperties.builder() + .image("pre-compute-image") + .fingerprint("pre-compute-fingerprint") + .build(); + private final TeeAppProperties postComputeProperties = TeeAppProperties.builder() + .image("post-compute-image") + .fingerprint("post-compute-fingerprint") + .build(); + private final SecretEnclaveBase preCompute = SecretEnclaveBase.builder() + .name("pre-compute") + .environment(Map.of("PRE_COMPUTE", "PRE_COMPUTE")) + .build(); + private final SecretEnclaveBase appCompute = SecretEnclaveBase.builder() + .name("app") + .environment(Map.of("APP_COMPUTE", "APP_COMPUTE")) + .build(); + private final SecretEnclaveBase postCompute = SecretEnclaveBase.builder() + .name("post-compute") + .environment(Map.of("POST_COMPUTE", "POST_COMPUTE")) + .build(); + + @Test + void shouldGenerateTdxSessionWithPreCompute() throws TeeSessionGenerationException { + final TeeEnclaveConfiguration enclaveConfig = TeeEnclaveConfiguration.builder() + .version(TDX_FRAMEWORK_VERSION) + .framework(TeeFramework.TDX) + .fingerprint(APP_FINGERPRINT) + .build(); + final TeeSessionRequest request = createSessionRequestBuilder(createTaskDescription(createDealParams().build(), enclaveConfig).build()) + .teeServicesProperties(new TdxServicesProperties(TDX_FRAMEWORK_VERSION, preComputeProperties, postComputeProperties)) + .build(); + + final SecretSessionBase secretSessionBase = SecretSessionBase.builder() + .preCompute(preCompute) + .appCompute(appCompute) + .postCompute(postCompute) + .build(); + when(secretSessionBaseService.getSecretsTokens(request)) + .thenReturn(secretSessionBase); + final TdxSession tdxSession = tdxSessionMakerService.generateSession(request); + final List services = List.of( + new TdxSession.Service(preCompute.getName(), "pre-compute-image", "pre-compute-fingerprint", Map.of("PRE_COMPUTE", "PRE_COMPUTE")), + new TdxSession.Service(appCompute.getName(), APP_URI, "", Map.of("APP_COMPUTE", "APP_COMPUTE")), + new TdxSession.Service(postCompute.getName(), "post-compute-image", "post-compute-fingerprint", Map.of("POST_COMPUTE", "POST_COMPUTE")) + ); + assertThat(tdxSession) + .usingRecursiveComparison() + .isEqualTo(new TdxSession(SESSION_ID, TDX_SESSION_VERSION, services)); + } + + @Test + void shouldGenerateTdxSessionWithoutPreCompute() throws TeeSessionGenerationException { + final TeeEnclaveConfiguration enclaveConfig = TeeEnclaveConfiguration.builder() + .version(TDX_FRAMEWORK_VERSION) + .framework(TeeFramework.TDX) + .fingerprint(APP_FINGERPRINT) + .build(); + final TeeSessionRequest request = createSessionRequestBuilder(createTaskDescription(createDealParams().build(), enclaveConfig).build()) + .teeServicesProperties(new TdxServicesProperties(TDX_FRAMEWORK_VERSION, preComputeProperties, postComputeProperties)) + .build(); + + final SecretSessionBase secretSessionBase = SecretSessionBase.builder() + .appCompute(appCompute) + .postCompute(postCompute) + .build(); + when(secretSessionBaseService.getSecretsTokens(request)) + .thenReturn(secretSessionBase); + final TdxSession tdxSession = tdxSessionMakerService.generateSession(request); + final List services = List.of( + new TdxSession.Service(appCompute.getName(), APP_URI, "", Map.of("APP_COMPUTE", "APP_COMPUTE")), + new TdxSession.Service(postCompute.getName(), "post-compute-image", "post-compute-fingerprint", Map.of("POST_COMPUTE", "POST_COMPUTE")) + ); + assertThat(tdxSession) + .usingRecursiveComparison() + .isEqualTo(new TdxSession(SESSION_ID, TDX_SESSION_VERSION, services)); + } +}