Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
## Unreleased
* Add normalized token audience configuration through Azure Managed client/worker options, convenience overloads, and connection-string `ResourceId`. Missing or empty audiences now default to `https://durabletask.azure.us` when `REGION_NAME` starts with `usgov` or `usdod` (case-insensitively), otherwise `https://durabletask.io`. Explicit audiences override this default; whitespace-only or otherwise empty normalized values are rejected.
* Add optional connection-string `AuthorityHost` forwarding for Azure Identity credentials that support it. Audience selection does not change the service endpoint or credential authority; managed identity and developer-tool cloud configuration remain separate.
* Add the `exporthistory` module for durable, checkpointed export of terminal orchestration history to Azure Blob Storage ([#293](https://github.com/microsoft/durabletask-java/pull/293))
* Add client APIs to list terminal instance IDs by completion time (`listInstanceIds`) and read orchestration history (`getOrchestrationHistory`) ([#292](https://github.com/microsoft/durabletask-java/pull/292))
* Add `createReplaySafeLogger` to suppress orchestration log output during replay ([#295](https://github.com/microsoft/durabletask-java/pull/295)).
Expand Down
93 changes: 93 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,99 @@ The following packages are produced from this repo.
| Durable Task - Client | [![Maven Central](https://img.shields.io/maven-central/v/com.microsoft/durabletask-client?label=durabletask-client)](https://mvnrepository.com/artifact/com.microsoft/durabletask-client/1.0.0) |
| Durable Task - Azure Functions | [![Maven Central](https://img.shields.io/maven-central/v/com.microsoft/durabletask-azure-functions?label=durabletask-azure-functions)](https://mvnrepository.com/artifact/com.microsoft/durabletask-azure-functions/1.0.1) |

## Azure Durable Task Scheduler authentication

The `com.microsoft:durabletask-azuremanaged` package configures clients and workers
through `DurableTaskSchedulerClientOptions`, `DurableTaskSchedulerWorkerOptions`,
or the corresponding `DurableTaskSchedulerClientExtensions` and
`DurableTaskSchedulerWorkerExtensions` convenience methods.

### Token audience

Set `resourceId` using `setResourceId(...)` on either options class, the optional
last argument of the `createClientBuilder`, `createWorkerBuilder`, and
`useDurableTaskScheduler` overloads, or `ResourceId` in a connection string.
This is a **token audience URI**, not an Azure Resource Manager resource path.
Existing overloads remain supported.

| Configuration | Selected audience |
| --- | --- |
| Explicit nonempty `resourceId` / `ResourceId` | The normalized explicit value |
| Missing, null, or empty value, with `REGION_NAME` starting with `usgov` or `usdod` (case-insensitive) | `https://durabletask.azure.us` |
| All other cases | `https://durabletask.io` |

**Default behavior change:** applications running in US Government or DoD regions
now select the government audience when no explicit audience is provided.
Set `ResourceId=https://durabletask.io` to retain the public audience in those
regions. Prefixes, not substrings, are matched: `chinaeast2`, `notusgov`, and
`notusdod` still use the public default. No audience is inferred from the endpoint.

Explicit values have surrounding whitespace and trailing `/` characters removed,
then one existing `/.default` suffix removed case-insensitively, followed by any
remaining trailing `/` characters. URI casing is otherwise preserved. For example,
`https://durabletask.azure.us//.DEFAULT//` requests
`https://durabletask.azure.us/.default`, and `api://CustomAudience/resource/.DEFAULT/`
requests `api://CustomAudience/resource/.default`. Whitespace-only input, `///`,
`/.default`, and `/.DEFAULT///` throw `IllegalArgumentException`; use an omitted
or genuinely empty value for the default.

Defaults are resolved per options instance or parsed connection string, rather
than at class initialization. Setting a null or empty audience explicitly resolves
the default again at that point. The selected audience is retained when creating
channels, refreshing tokens, and reconnecting. Connection-string conversion does
not normalize the audience again.

### Government-cloud example and credential authority

The **audience**, **credential authority/cloud**, and **service endpoint** are
independent settings. Neither `resourceId` nor `REGION_NAME` changes the endpoint
or credential authority. For an already-created `TokenCredential`, configure
authority on that credential; token requests do not override it.

```java
import com.azure.core.credential.TokenCredential;
import com.azure.identity.AzureAuthorityHosts;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.microsoft.durabletask.DurableTaskGrpcClientBuilder;
import com.microsoft.durabletask.DurableTaskGrpcWorkerBuilder;
import com.microsoft.durabletask.azuremanaged.DurableTaskSchedulerClientExtensions;
import com.microsoft.durabletask.azuremanaged.DurableTaskSchedulerWorkerExtensions;

// Set these to your scheduler's actual endpoint and task hub.
String endpoint = System.getenv("DTS_ENDPOINT");
String taskHub = System.getenv("DTS_TASK_HUB");
TokenCredential credential = new DefaultAzureCredentialBuilder()
.authorityHost(AzureAuthorityHosts.AZURE_GOVERNMENT)
.build();

DurableTaskGrpcClientBuilder clientBuilder =
DurableTaskSchedulerClientExtensions.createClientBuilder(
endpoint, taskHub, credential, "https://durabletask.azure.us");
DurableTaskGrpcWorkerBuilder workerBuilder =
DurableTaskSchedulerWorkerExtensions.createWorkerBuilder(
endpoint, taskHub, credential, "https://durabletask.azure.us");
```

When the SDK constructs the credential from a connection string, use the optional
`AuthorityHost` property:

```text
Endpoint=<your-scheduler-endpoint>;TaskHub=<your-task-hub>;Authentication=DefaultAzure;ResourceId=https://durabletask.azure.us;AuthorityHost=https://login.microsoftonline.us/
```

`AuthorityHost` is forwarded to Azure Identity for `DefaultAzure`, `Environment`,
`WorkloadIdentity`, and `InteractiveBrowser` authentication. Omission or an empty
value leaves Azure Identity's defaults intact, including `AZURE_AUTHORITY_HOST`
where supported. It is not an authority override on client or worker options.

Managed identity uses the hosting environment's identity endpoint; an Entra
authority override does not apply. Developer-tool credentials (`AzureCli`,
`AzurePowerShell`, `VisualStudioCode`, and `IntelliJ`) use those tools' cloud
configuration, not the connection string's `AuthorityHost`. Configure them
separately, including when they are used by `DefaultAzureCredential` (for example,
`az cloud set --name AzureUSGovernment` before signing in with Azure CLI).
`Authentication=None` remains anonymous.

## Getting started with Azure Functions

For information about how to get started with Durable Functions for Java, see the [Azure Functions README.md](/azurefunctions/README.md) content.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,37 @@ public static void useDurableTaskScheduler(
String endpoint,
String taskHubName,
@Nullable TokenCredential tokenCredential) {
useDurableTaskScheduler(builder, endpoint, taskHubName, tokenCredential, null);
}

/**
* Configures a client builder with an explicit token audience.
*
* @param builder The builder to configure.
* @param endpoint The service endpoint, independent of the audience and credential authority.
* @param taskHubName The name of the task hub.
* @param tokenCredential The credential, with its authority/cloud configured by the caller,
* or null for anonymous access.
* @param resourceId The token audience URI, or null/empty for the region-based default.
* See {@link DurableTaskSchedulerClientOptions#setResourceId(String)}
* for normalization and default selection.
* @throws NullPointerException if builder, endpoint, or taskHubName is null.
* @throws IllegalArgumentException if resourceId becomes empty after normalization.
*/
public static void useDurableTaskScheduler(
DurableTaskGrpcClientBuilder builder,
String endpoint,
String taskHubName,
@Nullable TokenCredential tokenCredential,
@Nullable String resourceId) {
Objects.requireNonNull(builder, "builder must not be null");
Objects.requireNonNull(endpoint, "endpoint must not be null");
Objects.requireNonNull(taskHubName, "taskHubName must not be null");

configureBuilder(builder, new DurableTaskSchedulerClientOptions()
.setEndpointAddress(endpoint)
.setTaskHubName(taskHubName)
.setResourceId(resourceId)
.setCredential(tokenCredential));
}

Expand Down Expand Up @@ -85,12 +109,35 @@ public static DurableTaskGrpcClientBuilder createClientBuilder(
String endpoint,
String taskHubName,
@Nullable TokenCredential tokenCredential) {
return createClientBuilder(endpoint, taskHubName, tokenCredential, null);
}

/**
* Creates a client builder with an explicit token audience.
*
* @param endpoint The service endpoint, independent of the audience and credential authority.
* @param taskHubName The name of the task hub.
* @param tokenCredential The credential, with its authority/cloud configured by the caller,
* or null for anonymous access.
* @param resourceId The token audience URI, or null/empty for the region-based default.
* See {@link DurableTaskSchedulerClientOptions#setResourceId(String)}
* for normalization and default selection.
* @return A new configured DurableTaskGrpcClientBuilder instance.
* @throws NullPointerException if endpoint or taskHubName is null.
* @throws IllegalArgumentException if resourceId becomes empty after normalization.
*/
public static DurableTaskGrpcClientBuilder createClientBuilder(
String endpoint,
String taskHubName,
@Nullable TokenCredential tokenCredential,
@Nullable String resourceId) {
Objects.requireNonNull(endpoint, "endpoint must not be null");
Objects.requireNonNull(taskHubName, "taskHubName must not be null");

return createBuilderFromOptions(new DurableTaskSchedulerClientOptions()
.setEndpointAddress(endpoint)
.setTaskHubName(taskHubName)
.setResourceId(resourceId)
.setCredential(tokenCredential)
.setAllowInsecureCredentials(tokenCredential == null));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ public class DurableTaskSchedulerClientOptions {
private String taskHubName = "";

private TokenCredential credential;
private String resourceId = "https://durabletask.io";
private String resourceId = ResourceId.getDefault();
private boolean allowInsecureCredentials = false;
private Duration tokenRefreshMargin = Duration.ofMinutes(5);

/**
* Creates a new instance of DurableTaskSchedulerClientOptions.
* Resolves the token audience from {@code REGION_NAME} for this instance.
*
* @see #setResourceId(String)
*/
public DurableTaskSchedulerClientOptions() {
}
Expand All @@ -47,11 +50,12 @@ public static DurableTaskSchedulerClientOptions fromConnectionString(String conn
* @return A new DurableTaskSchedulerClientOptions object.
*/
static DurableTaskSchedulerClientOptions fromConnectionString(DurableTaskSchedulerConnectionString connectionString) {
// TODO: Parse different credential types from connection string
DurableTaskSchedulerClientOptions options = new DurableTaskSchedulerClientOptions();
options.setEndpointAddress(connectionString.getEndpoint());
options.setTaskHubName(connectionString.getTaskHubName());
options.setCredential(connectionString.getCredential());
// The connection string has already resolved and normalized this audience.
options.resourceId = connectionString.getResourceId();
options.setAllowInsecureCredentials(options.getCredential() == null);
return options;
}
Expand Down Expand Up @@ -108,7 +112,8 @@ public TokenCredential getCredential() {
/**
* Sets the credential used for authentication.
*
* @param credential The credential.
* @param credential The credential, or null for anonymous access. Configure the authority/cloud
* on the credential itself; the resource ID does not change its authority.
* @return This options object.
*/
public DurableTaskSchedulerClientOptions setCredential(TokenCredential credential) {
Expand All @@ -117,7 +122,7 @@ public DurableTaskSchedulerClientOptions setCredential(TokenCredential credentia
}

/**
* Gets the resource ID.
* Gets the normalized token audience URI (not an Azure Resource Manager resource path).
*
* @return The resource ID.
*/
Expand All @@ -126,13 +131,19 @@ public String getResourceId() {
}

/**
* Sets the resource ID.
*
* @param resourceId The resource ID.
* Sets the token audience URI, independently of the endpoint and credential authority.
* Surrounding whitespace, trailing slashes, and one case-insensitive {@code /.default}
* suffix are removed. Token requests append {@code /.default} to the result.
*
* @param resourceId The audience URI. Null or empty selects {@code https://durabletask.azure.us}
* when {@code REGION_NAME} starts with {@code usgov} or {@code usdod}
* (case-insensitively), otherwise {@code https://durabletask.io}.
* The selection is retained for subsequent channels and token refreshes.
* @return This options object.
* @throws IllegalArgumentException if a nonempty value becomes empty after normalization.
*/
public DurableTaskSchedulerClientOptions setResourceId(String resourceId) {
this.resourceId = resourceId;
this.resourceId = ResourceId.resolve(resourceId);
return this;
}

Expand Down
Loading
Loading