secretless (#360)
This commit is contained in:
Родитель
2b49b63feb
Коммит
1eef873847
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче