Azure / azure-sdk-for-java

This repository is for active development of the Azure SDK for Java. For consumers of the SDK we recommend visiting our public developer docs at https://docs.microsoft.com/java/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-java.
MIT License
2.33k stars 1.97k forks source link

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

Open NathanEckert opened 2 months ago

NathanEckert commented 2 months ago

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:

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):

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

github-actions[bot] commented 2 months ago

@ibrahimrabab @ibrandes @seanmcc-msft

github-actions[bot] commented 2 months ago

Thank you for your feedback. Tagging and routing to the team member best able to assist.