Skip to content

[BUG] Displayed filename changed with client side encryption client between bom 1.2.23 and 1.2.24 when listing blobs #41264

Open

Description

Describe the bug

When migrating from the bom 1.2.23 to 1.2.24, the filename displayed when listing the blobs changed from dir/entity to dir/%2Fentity

Exception or Stack Trace

java.lang.AssertionError: 
Expecting actual:
  ["dir1%2Fentity2", "entity1"]
to contain exactly in any order:
  ["entity1", "dir1/entity2"]
elements not found:
  ["dir1/entity2"]
and elements not expected:
  ["dir1%2Fentity2"]

To Reproduce

You shoud provide:

  • your connection string (method getConnectionString() which is not imported)
  • your encryption key (method getEncryptionKey() which is not imported)
  • your encryption algorithm (method getKeyEncryptionAlgorithm() which is not imported)
import static org.assertj.core.api.Assertions.assertThat;

import com.activeviam.cloud.test.internal.util.impl.CloudTestUtil;
import com.azure.core.cryptography.AsyncKeyEncryptionKey;
import com.azure.core.http.rest.PagedIterable;
import com.azure.security.keyvault.keys.cryptography.KeyEncryptionKeyClientBuilder;
import com.azure.security.keyvault.keys.models.JsonWebKey;
import com.azure.security.keyvault.keys.models.KeyOperation;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.BlobItem;
import com.azure.storage.blob.models.ListBlobsOptions;
import com.azure.storage.blob.specialized.cryptography.EncryptedBlobClient;
import com.azure.storage.blob.specialized.cryptography.EncryptedBlobClientBuilder;
import com.azure.storage.blob.specialized.cryptography.EncryptionVersion;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class TestAzureUpdateWithEncryptedDirectory {

  private static final String connectionString = getConnectionString();

  private static BlobContainerClient CONTAINER;
  private static final String CONTAINER_NAME = "TestAzureUpdateWithEncryptedDirectory".toLowerCase();
  private static AsyncKeyEncryptionKey keyEncryptionKey;
  private static String keyEncryptionAlgorithm;

  @BeforeAll
  public static void setUp() throws Exception {
    BlobServiceClient client = new BlobServiceClientBuilder().connectionString(connectionString).buildClient();
    initializeEncryptionCapabilities(
        TestAzureUpdateWithEncryptedDirectory.class.getName(),
        CloudTestUtil.getEncryptionKey(),
        CloudTestUtil.getKeyEncryptionAlgorithm());
    CONTAINER = client.getBlobContainerClient(CONTAINER_NAME);
    CONTAINER.deleteIfExists(); // useful when rerunning the test to clean up the data from previous tests
    Thread.sleep(Duration.ofSeconds(5)); // wait for deletion of the container
    CONTAINER.create();
  }

  @Test
  void testListEntitiesOnAzure() {
    final String e1 = "entity1";
    final String e2 = "dir1/entity2";
    PagedIterable<BlobItem> blobs;

    blobs = CONTAINER.listBlobs(new ListBlobsOptions().setPrefix(""), null);
    assertThat(blobs.stream().map(BlobItem::getName)).isEmpty();

    uploadData(e1, "1");
    blobs = CONTAINER.listBlobs(new ListBlobsOptions().setPrefix(""), null);
    assertThat(blobs.stream().map(BlobItem::getName)).containsExactlyInAnyOrder(e1);

    uploadData(e2, "2");
    blobs = CONTAINER.listBlobs(new ListBlobsOptions().setPrefix(""), null);
    assertThat(blobs.stream().map(BlobItem::getName)).containsExactlyInAnyOrder(e1, e2);
  }


  private void uploadData(final String name, final String content) {
    try {

      final byte[] buffer = content.getBytes(Charset.defaultCharset());
      final EncryptedBlobClient encryptionClient =
          new EncryptedBlobClientBuilder(EncryptionVersion.V2)
              .blobClient(CONTAINER.getBlobClient(name))
              .key(keyEncryptionKey, keyEncryptionAlgorithm)
              .buildEncryptedBlobClient();

      encryptionClient.upload(new ByteArrayInputStream(buffer), buffer.length);

    } catch (final Exception e) {
      throw new RuntimeException(name, e);
    }
  }

  private static void initializeEncryptionCapabilities(
      final String keyId, final KeyPair keyPair, final String encryptionAlgorithm) {
    keyEncryptionKey = buildKeyEncryptionKeyFrom(keyId, keyPair, List.of(KeyOperation.UNWRAP_KEY, KeyOperation.WRAP_KEY));
    keyEncryptionAlgorithm = encryptionAlgorithm;
  }

  private static AsyncKeyEncryptionKey buildKeyEncryptionKeyFrom(
      final String keyId, final KeyPair keyPair, final List<KeyOperation> authorizedOperations) {
    final JsonWebKey jsonKey = JsonWebKey.fromRsa(keyPair, authorizedOperations).setId(keyId);
    return new KeyEncryptionKeyClientBuilder().buildAsyncKeyEncryptionKey(jsonKey).block();
  }
}

Expected behavior

It feels like a regression, especially as this behavior differs from the non-encrypted client

Setup (please complete the following information):

  • OS: Linux 6.5.0-44-generic 22.04.1-Ubuntu
  • IDE: IntelliJ
  • Library/Libraries: bom 1.2.23 to 1.2.24, using com.azure:azure-storage-blob and com.azure:azure-storage-blob-cryptography
  • Java version: Java version: 21.0.3, vendor: Eclipse Adoptium
  • App Server/Environment: Local
  • Frameworks: None

Information Checklist
Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issuer as an incomplete report

  • Bug Description Added
  • Repro Steps Added
  • Setup information Added
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

ClientThis issue points to a problem in the data-plane of the library.StorageStorage Service (Queues, Blobs, Files)customer-reportedIssues that are reported by GitHub users external to the Azure organization.needs-team-attentionWorkflow: This issue needs attention from Azure service team or SDK teamquestionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions