This commit is contained in:
AsafMah 2024-04-11 10:18:20 +03:00 коммит произвёл GitHub
Родитель 2b49b63feb
Коммит 1eef873847
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 109 добавлений и 32 удалений

22
.github/workflows/build.yml поставляемый
Просмотреть файл

@ -4,30 +4,29 @@ name: Build Java
on: on:
push: push:
branches: [ '**' ] branches: [ 'master' ]
pull_request: pull_request:
branches: [ '**' ] branches: [ '**' ]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: build
permissions: permissions:
checks: write checks: write
pull-requests: write pull-requests: write
id-token: write
contents: read
strategy: strategy:
matrix: matrix:
java: ['8', '11','17','21'] java: ['8', '11','17','21']
name: Java ${{ matrix.java }} name: Java ${{ matrix.java }}
steps: steps:
# Uncomment to run locally with "act" - name: Azure login
# - name: Download Maven uses: azure/login@v2
# run: | with:
# curl -sL https://www-eu.apache.org/dist/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.zip -o maven.zip client-id: ${{ secrets.APP_ID }}
# apt-get update tenant-id: ${{ secrets.TENANT_ID }}
# apt-get -y install unzip subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# unzip -d /usr/share maven.zip
# rm maven.zip
# ln -s /usr/share/apache-maven-3.8.4/bin/mvn /usr/bin/mvn
# echo "M2_HOME=/usr/share/apache-maven-3.8.4" | tee -a /etc/environment
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Setup java ${{ matrix.java }} - name: Setup java ${{ matrix.java }}
uses: actions/setup-java@v3 uses: actions/setup-java@v3
@ -42,7 +41,6 @@ jobs:
ENGINE_CONNECTION_STRING: https://sdkse2etest.eastus.kusto.windows.net ENGINE_CONNECTION_STRING: https://sdkse2etest.eastus.kusto.windows.net
TEST_DATABASE: e2e TEST_DATABASE: e2e
APP_ID: ${{ secrets.APP_ID }} APP_ID: ${{ secrets.APP_ID }}
APP_KEY: ${{ secrets.APP_KEY }}
TENANT_ID: ${{ secrets.TENANT_ID }} TENANT_ID: ${{ secrets.TENANT_ID }}
CI_EXECUTION: 1 CI_EXECUTION: 1
- name: Publish Unit Test Results - name: Publish Unit Test Results

9
.github/workflows/release.yml поставляемый
Просмотреть файл

@ -5,6 +5,7 @@ permissions:
packages: write packages: write
deployments: write deployments: write
contents: write contents: write
id-token: write
on: on:
push: push:
@ -32,6 +33,7 @@ on:
jobs: jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: build
strategy: strategy:
matrix: matrix:
java: [ '8' ] java: [ '8' ]
@ -40,6 +42,12 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
ref: ${{ github.event.inputs.tag || github.ref }} ref: ${{ github.event.inputs.tag || github.ref }}
- name: Azure login
uses: azure/login@v2
with:
client-id: ${{ secrets.APP_ID }}
tenant-id: ${{ secrets.TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Setup java ${{ matrix.java }} - name: Setup java ${{ matrix.java }}
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
@ -53,7 +61,6 @@ jobs:
ENGINE_CONNECTION_STRING: https://sdkse2etest.eastus.kusto.windows.net ENGINE_CONNECTION_STRING: https://sdkse2etest.eastus.kusto.windows.net
TEST_DATABASE: e2e TEST_DATABASE: e2e
APP_ID: ${{ secrets.APP_ID }} APP_ID: ${{ secrets.APP_ID }}
APP_KEY: ${{ secrets.APP_KEY }}
TENANT_ID: ${{ secrets.TENANT_ID }} TENANT_ID: ${{ secrets.TENANT_ID }}
CI_EXECUTION: 1 CI_EXECUTION: 1
- name: Get version - name: Get version

Просмотреть файл

@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Added
- Azure CLI authentication
## [6.0.0] - 2024-03-07 ## [6.0.0] - 2024-03-07
### Changed ### Changed

Просмотреть файл

@ -0,0 +1,51 @@
package com.microsoft.azure.kusto.data.auth;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenRequestContext;
import com.azure.core.http.HttpClient;
import com.azure.identity.AzureCliCredential;
import com.azure.identity.AzureCliCredentialBuilder;
import com.microsoft.azure.kusto.data.exceptions.DataClientException;
import com.microsoft.azure.kusto.data.exceptions.DataServiceException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.net.URISyntaxException;
public class AzureCliTokenProvider extends CloudDependentTokenProviderBase {
public static final String AZURE_CLI_TOKEN_PROVIDER = "AzureCliTokenProvider";
private TokenRequestContext tokenRequestContext;
private AzureCliCredential azureCliCredential;
public AzureCliTokenProvider(@NotNull String clusterUrl, @Nullable HttpClient httpClient) throws URISyntaxException {
super(clusterUrl, httpClient);
}
@Override
protected void initializeWithCloudInfo(CloudInfo cloudInfo) throws DataServiceException, DataClientException {
super.initializeWithCloudInfo(cloudInfo);
AzureCliCredentialBuilder builder = new AzureCliCredentialBuilder();
if (httpClient != null) {
builder = builder.httpClient(new HttpClientWrapper(httpClient));
}
this.azureCliCredential = builder.build();
tokenRequestContext = new TokenRequestContext().addScopes(scopes.toArray(new String[0]));
}
@Override
protected String acquireAccessTokenImpl() throws DataServiceException {
AccessToken accessToken = azureCliCredential.getToken(tokenRequestContext).block();
if (accessToken == null) {
throw new DataServiceException(clusterUrl, "Couldn't get token from Azure Identity", true);
}
return accessToken.getToken();
}
@Override
protected String getAuthMethod() {
return AZURE_CLI_TOKEN_PROVIDER;
}
}

Просмотреть файл

@ -33,6 +33,7 @@ public class ConnectionStringBuilder {
private String managedIdentityClientId; private String managedIdentityClientId;
private boolean useDeviceCodeAuth; private boolean useDeviceCodeAuth;
private boolean useManagedIdentityAuth; private boolean useManagedIdentityAuth;
private boolean useAzureCli;
private boolean useUserPromptAuth; private boolean useUserPromptAuth;
private String userNameForTracing; private String userNameForTracing;
private String appendedClientVersionForTracing; private String appendedClientVersionForTracing;
@ -87,6 +88,7 @@ public class ConnectionStringBuilder {
this.managedIdentityClientId = null; this.managedIdentityClientId = null;
this.useDeviceCodeAuth = false; this.useDeviceCodeAuth = false;
this.useManagedIdentityAuth = false; this.useManagedIdentityAuth = false;
this.useAzureCli = false;
this.useUserPromptAuth = false; this.useUserPromptAuth = false;
this.userNameForTracing = null; this.userNameForTracing = null;
this.appendedClientVersionForTracing = null; this.appendedClientVersionForTracing = null;
@ -145,6 +147,7 @@ public class ConnectionStringBuilder {
this.accessToken = other.accessToken; this.accessToken = other.accessToken;
this.tokenProvider = other.tokenProvider; this.tokenProvider = other.tokenProvider;
this.managedIdentityClientId = other.managedIdentityClientId; this.managedIdentityClientId = other.managedIdentityClientId;
this.useAzureCli = other.useAzureCli;
this.useDeviceCodeAuth = other.useDeviceCodeAuth; this.useDeviceCodeAuth = other.useDeviceCodeAuth;
this.useManagedIdentityAuth = other.useManagedIdentityAuth; this.useManagedIdentityAuth = other.useManagedIdentityAuth;
this.useUserPromptAuth = other.useUserPromptAuth; this.useUserPromptAuth = other.useUserPromptAuth;
@ -238,6 +241,10 @@ public class ConnectionStringBuilder {
return useManagedIdentityAuth; return useManagedIdentityAuth;
} }
boolean isUseAzureCli() {
return useAzureCli;
}
boolean isUseUserPromptAuth() { boolean isUseUserPromptAuth() {
return useUserPromptAuth; return useUserPromptAuth;
} }
@ -478,6 +485,17 @@ public class ConnectionStringBuilder {
return csb; return csb;
} }
public static ConnectionStringBuilder createWithAzureCli(String clusterUrl) {
if (StringUtils.isEmpty(clusterUrl)) {
throw new IllegalArgumentException("clusterUrl cannot be null or empty");
}
ConnectionStringBuilder csb = new ConnectionStringBuilder();
csb.clusterUrl = clusterUrl;
csb.useAzureCli = true;
return csb;
}
/** /**
* Sets the application name and username for Kusto connectors. * Sets the application name and username for Kusto connectors.
* *

Просмотреть файл

@ -48,6 +48,8 @@ public class TokenProviderFactory {
return new DeviceAuthTokenProvider(clusterUrl, authorityId, httpClient); return new DeviceAuthTokenProvider(clusterUrl, authorityId, httpClient);
} else if (csb.isUseManagedIdentityAuth()) { } else if (csb.isUseManagedIdentityAuth()) {
return new ManagedIdentityTokenProvider(clusterUrl, csb.getManagedIdentityClientId(), httpClient); return new ManagedIdentityTokenProvider(clusterUrl, csb.getManagedIdentityClientId(), httpClient);
} else if (csb.isUseAzureCli()) {
return new AzureCliTokenProvider(clusterUrl, httpClient);
} else if (csb.isUseUserPromptAuth()) { } else if (csb.isUseUserPromptAuth()) {
if (StringUtils.isNotBlank(csb.getUserUsernameHint())) { if (StringUtils.isNotBlank(csb.getUserUsernameHint())) {
String usernameHint = csb.getUserUsernameHint(); String usernameHint = csb.getUserUsernameHint();

Просмотреть файл

@ -33,6 +33,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch; import org.apache.commons.lang3.time.StopWatch;
import org.apache.http.conn.util.InetAddressUtils; import org.apache.http.conn.util.InetAddressUtils;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
@ -65,7 +66,7 @@ class E2ETest {
private static StreamingClient streamingClient; private static StreamingClient streamingClient;
private static final String databaseName = System.getenv("TEST_DATABASE"); private static final String databaseName = System.getenv("TEST_DATABASE");
private static final String appId = System.getenv("APP_ID"); private static final String appId = System.getenv("APP_ID");
private static String appKey; private static final String appKey = System.getenv("APP_KEY");
private static final String tenantId = System.getenv().getOrDefault("TENANT_ID", "microsoft.com"); private static final String tenantId = System.getenv().getOrDefault("TENANT_ID", "microsoft.com");
private static String principalFqn; private static String principalFqn;
private static String resourcesPath; private static String resourcesPath;
@ -77,22 +78,12 @@ class E2ETest {
private final ObjectMapper objectMapper = Utils.getObjectMapper(); private final ObjectMapper objectMapper = Utils.getObjectMapper();
@BeforeAll @BeforeAll
public static void setUp() throws IOException { public static void setUp() {
appKey = System.getenv("APP_KEY");
if (appKey == null) {
String secretPath = System.getProperty("SecretPath");
if (secretPath == null) {
throw new IllegalArgumentException("SecretPath is not set");
}
appKey = Files.readAllLines(Paths.get(secretPath)).get(0);
}
tableName = "JavaTest_" + new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss_SSS").format(Calendar.getInstance().getTime()) + "_" tableName = "JavaTest_" + new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss_SSS").format(Calendar.getInstance().getTime()) + "_"
+ ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); + ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);
principalFqn = String.format("aadapp=%s;%s", appId, tenantId); principalFqn = String.format("aadapp=%s;%s", appId, tenantId);
ConnectionStringBuilder dmCsb = ConnectionStringBuilder.createWithAadApplicationCredentials(System.getenv("DM_CONNECTION_STRING"), appId, appKey, ConnectionStringBuilder dmCsb = createConnection(System.getenv("DM_CONNECTION_STRING"));
tenantId);
dmCsb.setUserNameForTracing("testUser"); dmCsb.setUserNameForTracing("testUser");
try { try {
dmCslClient = ClientFactory.createClient(dmCsb); dmCslClient = ClientFactory.createClient(dmCsb);
@ -107,8 +98,7 @@ class E2ETest {
Assertions.fail("Failed to create ingest client", ex); Assertions.fail("Failed to create ingest client", ex);
} }
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadApplicationCredentials(System.getenv("ENGINE_CONNECTION_STRING"), appId, ConnectionStringBuilder engineCsb = createConnection(System.getenv("ENGINE_CONNECTION_STRING"));
appKey, tenantId);
engineCsb.setUserNameForTracing("Java_E2ETest_ø"); engineCsb.setUserNameForTracing("Java_E2ETest_ø");
try { try {
streamingIngestClient = IngestClientFactory.createStreamingIngestClient(engineCsb); streamingIngestClient = IngestClientFactory.createStreamingIngestClient(engineCsb);
@ -123,6 +113,15 @@ class E2ETest {
createTestData(); createTestData();
} }
private static @NotNull ConnectionStringBuilder createConnection(String connectionString) {
if (appKey == null) {
return ConnectionStringBuilder.createWithAzureCli(connectionString);
}
return ConnectionStringBuilder.createWithAadApplicationCredentials(connectionString, appId, appKey,
tenantId);
}
@AfterAll @AfterAll
public static void tearDown() { public static void tearDown() {
try { try {
@ -466,13 +465,14 @@ class E2ETest {
@Test @Test
void testCreateWithAadApplicationCredentials() { void testCreateWithAadApplicationCredentials() {
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadApplicationCredentials(System.getenv("ENGINE_CONNECTION_STRING"), appId, Assumptions.assumeTrue(appKey != null);
appKey, tenantId); ConnectionStringBuilder engineCsb = createConnection(System.getenv("ENGINE_CONNECTION_STRING"));
assertTrue(canAuthenticate(engineCsb)); assertTrue(canAuthenticate(engineCsb));
} }
@Test @Test
void testCreateWithConnectionStringAndAadApplicationCredentials() { void testCreateWithConnectionStringAndAadApplicationCredentials() {
Assumptions.assumeTrue(appKey != null);
ConnectionStringBuilder engineCsb = new ConnectionStringBuilder( ConnectionStringBuilder engineCsb = new ConnectionStringBuilder(
"Data Source=" + System.getenv("ENGINE_CONNECTION_STRING") + ";AppClientId=" + appId + ";AppKey=" + appKey + ";Authority ID=" + tenantId); "Data Source=" + System.getenv("ENGINE_CONNECTION_STRING") + ";AppClientId=" + appId + ";AppKey=" + appKey + ";Authority ID=" + tenantId);
assertTrue(canAuthenticate(engineCsb)); assertTrue(canAuthenticate(engineCsb));
@ -606,8 +606,7 @@ class E2ETest {
@Test @Test
void testSameHttpClientInstance() throws DataClientException, DataServiceException, URISyntaxException { void testSameHttpClientInstance() throws DataClientException, DataServiceException, URISyntaxException {
ConnectionStringBuilder engineCsb = ConnectionStringBuilder.createWithAadApplicationCredentials(System.getenv("ENGINE_CONNECTION_STRING"), appId, ConnectionStringBuilder engineCsb = createConnection(System.getenv("ENGINE_CONNECTION_STRING"));
appKey, tenantId);
HttpClient httpClient = HttpClientFactory.create(null); HttpClient httpClient = HttpClientFactory.create(null);
HttpClient httpClientSpy = Mockito.spy(httpClient); HttpClient httpClientSpy = Mockito.spy(httpClient);
Client clientImpl = ClientFactory.createClient(engineCsb, httpClientSpy); Client clientImpl = ClientFactory.createClient(engineCsb, httpClientSpy);