Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EventGrid AAD authentication #22735

Merged
merged 16 commits into from
Jul 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion sdk/eventgrid/azure-messaging-eventgrid/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Release History

## 4.5.0-beta.1 (Unreleased)

### Features Added
- Added `EventGridPublisherClientBuilder#credential(TokenCredential credential)` to support Azure Active Directory authentication.

## 4.4.0 (2021-06-09)
### New Features
Expand Down
32 changes: 29 additions & 3 deletions sdk/eventgrid/azure-messaging-eventgrid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ az eventgrid domain create --location <location> --resource-group <your-resource

### Authenticate the Client

In order to send events, we need an endpoint to send to and some authentication for the endpoint, either as a
key credential or a shared access signature.
In order to send events, we need an endpoint to send to and some authentication for the endpoint. The authentication can be
a key credential, a shared access signature, or Azure Active Directory token authentication.
The endpoint and key can both be obtained through [Azure Portal][portal] or [Azure CLI][cli].

#### Endpoint
Expand All @@ -75,6 +75,11 @@ using the following command in [Azure CLI][cli]. Anyone of the keys listed will
az eventgrid topic key list --name <your-resource-name> --resource-group <your-resource-group-name>
```

#### Azure Active Directory (AAD) Token authentication
Azure Event Grid provides integration with Azure Active Directory (Azure AD) for identity-based authentication of requests.
With Azure AD, you can use role-based access control (RBAC) to grant access to your Azure Event Grid resources to users, groups, or applications.
To send events to a topic or domain with a `TokenCredential`, the authenticated identity should have the "EventGrid Data Sender" role assigned.

#### Creating the Client

##### Using endpoint and access key to create the client
Expand Down Expand Up @@ -138,7 +143,7 @@ limited time, you can use it to create the publisher client:
Sync client:
<!-- embedme ./src/samples/java/com/azure/messaging/eventgrid/samples/ReadmeSamples.java#L102-L105 -->
```java
EventGridPublisherClient<CloudEvent> eventGridEventClient = new EventGridPublisherClientBuilder()
EventGridPublisherClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpont of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new AzureSasCredential("<sas token that can access the endpoint>"))
.buildCloudEventPublisherClient();
Expand All @@ -152,6 +157,27 @@ EventGridPublisherAsyncClient<CloudEvent> cloudEventAsyncClient = new EventGridP
.buildCloudEventPublisherAsyncClient();
```

##### Using endpoint and Azure Active Directory (AAD) token credential to create the client
YijunXieMS marked this conversation as resolved.
Show resolved Hide resolved
To use the AAD token credential, include `azure-identity` artifact as a dependency. Refer to
[azure-identity README](https://docs.microsoft.com/java/api/overview/azure/identity-readme) for details.

Sync client:
<!-- embedme ./src/samples/java/com/azure/messaging/eventgrid/samples/ReadmeSamples.java#L231-L234 -->
```java
EventGridPublisherClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherClient();
```
Async client:
<!-- embedme ./src/samples/java/com/azure/messaging/eventgrid/samples/ReadmeSamples.java#L238-L241 -->
```java
EventGridPublisherAsyncClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherAsyncClient();
```

#### Create a SAS token for other people to send events for a limited period of time
If you'd like to give permission to other people to publish events to your Event Grid Topic or Domain for some time, you can create
a SAS (**Shared Access Signature**) for them so they can create an `EventGridPublisherClient` like the above to use `AzureSasCredential`
Expand Down
6 changes: 6 additions & 0 deletions sdk/eventgrid/azure-messaging-eventgrid/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
<version>12.10.0</version> <!-- {x-version-update;com.azure:azure-storage-queue;dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.3.3</version> <!-- {x-version-update;com.azure:azure-identity;dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.azure.core.annotation.ServiceClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.credential.AzureSasCredential;
import com.azure.core.credential.TokenCredential;
import com.azure.core.http.HttpClient;
import com.azure.core.http.HttpHeader;
import com.azure.core.http.HttpHeaders;
Expand All @@ -14,6 +15,7 @@
import com.azure.core.http.policy.AddDatePolicy;
import com.azure.core.http.policy.AddHeadersPolicy;
import com.azure.core.http.policy.AzureKeyCredentialPolicy;
import com.azure.core.http.policy.BearerTokenAuthenticationPolicy;
import com.azure.core.http.policy.HttpLogOptions;
import com.azure.core.http.policy.HttpLoggingPolicy;
import com.azure.core.http.policy.HttpPipelinePolicy;
Expand Down Expand Up @@ -53,6 +55,7 @@ public final class EventGridPublisherClientBuilder {
private static final String EVENTGRID_PROPERTIES = "azure-messaging-eventgrid.properties";
private static final String NAME = "name";
private static final String VERSION = "version";
private static final String DEFAULT_EVENTGRID_SCOPE = "https://eventgrid.azure.net/.default";

private final String clientName;

Expand All @@ -70,6 +73,8 @@ public final class EventGridPublisherClientBuilder {

private AzureSasCredential sasToken;

private TokenCredential tokenCredential;

private EventGridServiceVersion serviceVersion;

private String endpoint;
Expand Down Expand Up @@ -131,14 +136,29 @@ private <T> EventGridPublisherAsyncClient<T> buildAsyncClient(Class<T> eventClas

httpPipelinePolicies.add(new AddDatePolicy());

// Using token before key if both are set
final int credentialCount = (sasToken != null ? 1 : 0) + (keyCredential != null ? 1 : 0)
+ (tokenCredential != null ? 1 : 0);
if (credentialCount > 1) {
throw logger.logExceptionAsError(
new IllegalStateException("More than 1 credentials are set while building a client. "
+ "You should set one and only one credential of type 'TokenCredential', 'AzureSasCredential', "
+ "or 'AzureKeyCredential'."));
} else if (credentialCount == 0) {
throw logger.logExceptionAsError(
new IllegalStateException("Missing credential information while building a client."
+ "You should set one and only one credential of type 'TokenCredential', 'AzureSasCredential', "
+ "or 'AzureKeyCredential'."));
}
if (sasToken != null) {
httpPipelinePolicies.add((context, next) -> {
context.getHttpRequest().getHeaders().set(AEG_SAS_TOKEN, sasToken.getSignature());
return next.process();
});
} else {
} else if (keyCredential != null) {
httpPipelinePolicies.add(new AzureKeyCredentialPolicy(AEG_SAS_KEY, keyCredential));
} else {
httpPipelinePolicies.add(new BearerTokenAuthenticationPolicy(this.tokenCredential,
DEFAULT_EVENTGRID_SCOPE));
YijunXieMS marked this conversation as resolved.
Show resolved Hide resolved
}

httpPipelinePolicies.addAll(policies);
Expand Down Expand Up @@ -238,7 +258,7 @@ public EventGridPublisherClientBuilder credential(AzureKeyCredential credential)

/**
* Set the domain or topic authentication using an already obtained Shared Access Signature token.
* @param credential the token credential to use.
* @param credential the sas credential to use.
*
* @return the builder itself.
*/
Expand All @@ -247,6 +267,19 @@ public EventGridPublisherClientBuilder credential(AzureSasCredential credential)
return this;
}

/**
* Set the domain or topic authentication using Azure Activity Directory authentication.
* Refer to <a href="https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/identity/azure-identity">azure-identity</a>
YijunXieMS marked this conversation as resolved.
Show resolved Hide resolved
*
* @param credential the token credential to use.
*
* @return the builder itself.
*/
public EventGridPublisherClientBuilder credential(TokenCredential credential) {
this.tokenCredential = credential;
return this;
}

/**
* Set the domain or topic endpoint. This is the address to publish events to.
* It must be the full url of the endpoint instead of just the hostname.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.azure.core.util.BinaryData;
import com.azure.core.util.serializer.TypeReference;
import com.azure.core.models.CloudEvent;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.messaging.eventgrid.EventGridEvent;
import com.azure.messaging.eventgrid.EventGridPublisherAsyncClient;
import com.azure.messaging.eventgrid.EventGridPublisherClient;
Expand Down Expand Up @@ -99,8 +100,8 @@ public void createCustomEventPublisherAsyncClient() {
}

public void createPublisherClientWithSas() {
EventGridPublisherClient<CloudEvent> eventGridEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpont of your event grid topic/domain that accepts CloudEvent schema>")
EventGridPublisherClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new AzureSasCredential("<sas token that can access the endpoint>"))
.buildCloudEventPublisherClient();
}
Expand Down Expand Up @@ -224,4 +225,18 @@ public void systemEventDifferentEventData() {
}
}
}

public void createPublisherClientWithTokenCredential() {
EventGridPublisherClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherClient();
}

public void createPublisherAsyncClientWithTokenCredential() {
EventGridPublisherAsyncClient<CloudEvent> cloudEventClient = new EventGridPublisherClientBuilder()
.endpoint("<endpoint of your event grid topic/domain that accepts CloudEvent schema>")
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherAsyncClient();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package com.azure.messaging.eventgrid;

import com.azure.core.credential.AzureKeyCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import org.junit.jupiter.api.Test;

import java.io.UnsupportedEncodingException;
Expand All @@ -15,6 +16,7 @@
import java.time.format.DateTimeFormatter;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class EventGridPublisherClientOnlyTests {
Expand Down Expand Up @@ -49,4 +51,30 @@ public void testGenerateSas() throws UnsupportedEncodingException {

assertTrue(sasToken1.contains(unsignedSas));
}

@Test
public void createPublisherClientWithNoCredentials() {
final String fullEndpoint = String.format("%s?%s=%s", DUMMY_ENDPOINT, "api-version",
EventGridServiceVersion.getLatest().getVersion());

assertThrows(IllegalStateException.class, () -> {
new EventGridPublisherClientBuilder()
.endpoint(fullEndpoint)
.buildCloudEventPublisherClient();
});
}

@Test
public void createPublisherClientWithTwoCredentials() {
final String fullEndpoint = String.format("%s?%s=%s", DUMMY_ENDPOINT, "api-version",
EventGridServiceVersion.getLatest().getVersion());

assertThrows(IllegalStateException.class, () -> {
new EventGridPublisherClientBuilder()
.endpoint(fullEndpoint)
.credential(new AzureKeyCredential("FakeCredential"))
.credential(new DefaultAzureCredentialBuilder().build())
.buildCloudEventPublisherClient();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import com.azure.core.util.Context;
import com.azure.core.models.CloudEvent;
import com.azure.core.util.serializer.JacksonAdapter;
import com.azure.identity.DefaultAzureCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
Expand Down Expand Up @@ -160,6 +162,32 @@ public void publishWithSasToken() {
.verifyComplete();
}

@Test
@Disabled
public void publishWithTokenCredential() {
DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder().build();
EventGridPublisherAsyncClient<CloudEvent> egClient = builder
.credential(defaultCredential)
.endpoint(getEndpoint(CLOUD_ENDPOINT))
.buildCloudEventPublisherAsyncClient();

List<CloudEvent> events = new ArrayList<>();
events.add(new CloudEvent("/microsoft/testEvent", "Microsoft.MockPublisher.TestEvent",
BinaryData.fromObject(new HashMap<String, String>() {
{
put("Field1", "Value1");
put("Field2", "Value2");
put("Field3", "Value3");
}
}), CloudEventDataFormat.JSON, "application/json")
.setSubject("Test")
.setTime(OffsetDateTime.now()));

StepVerifier.create(egClient.sendEventsWithResponse(events, Context.NONE))
.expectNextMatches(voidResponse -> voidResponse.getStatusCode() == 200)
.verifyComplete();
}

@Test
public void publishCloudEvents() {
EventGridPublisherAsyncClient<CloudEvent> egClient = builder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"networkCallRecords" : [ {
"Method" : "POST",
"Uri" : "https://REDACTED.westus2-1.eventgrid-int.azure.net/api/events?api-version=2018-01-01",
"Headers" : {
"User-Agent" : "azsdk-java-UnknownName/UnknownVersion (15.0.2; Windows 10; 10.0)",
"x-ms-client-request-id" : "d9d08daa-f908-4338-ab35-ad6042dc5b9c",
"Content-Type" : "application/cloudevents-batch+json; charset=utf-8"
},
"Response" : {
"content-length" : "0",
"Strict-Transport-Security" : "max-age=31536000; includeSubDomains",
"Server" : "Microsoft-HTTPAPI/2.0",
"api-supported-versions" : "2018-01-01",
"retry-after" : "0",
"StatusCode" : "200",
"x-ms-request-id" : "83028bf1-abe4-49ee-a244-ad5e66335d25",
"Date" : "Fri, 25 Jun 2021 21:19:01 GMT"
},
"Exception" : null
} ],
"variables" : [ ]
}