The most recent patch for this version is 26.2.1.  Learn more

Skip to main content

Custom delivery

With the custom delivery model, APD builds domain.yaml and your pipeline delivers it to storage that ADS reads: a shared drive, a Kubernetes volume, a server, or a repository such as Artifactory. ADS reloads the domain when the file changes.

Compared with the other delivery models:

  • With the Authorization Hub model, ADS loads the domain from Authorization Hub. Custom delivery needs no control plane: you use storage you already have.
  • With the container image model, every policy change needs a new image and a redeployment. With custom delivery, you build and deploy the ADS image once, and a policy change only replaces domain.yaml. This suits teams that change policies often, need to deliver a fix quickly, or share one ADS image across environments.

See Sample CI/CD pipeline for pipelines that use custom delivery.

Configure ADS to reload the domain​

ADS reads the domain from the file in domain.path of deployment.yaml. In APD's deployment.yaml the path is set with the environment variable ADS_DOMAIN. Point it to the delivered file, for example ADS_DOMAIN=/opt/ads/domain/domain.yaml, where the shared storage is mounted.

To make ADS reload the domain when the file changes, set refreshInterval in the domain section of deployment.yaml. For projects created with APD 26.2 or later, uncomment this line. For older projects, add it manually:

deployment.yaml
domain:
path: ${ADS_DOMAIN:./domain.yaml}
refreshInterval: 30 seconds
  • ADS checks the file at this interval and reloads the domain only when its content has changed. Requests are served during the reload, without downtime.
  • The interval is a number and a unit, such as 30 seconds, 1 minute or 1 hour. The minimum is 1 second; 0 seconds is rejected when ADS starts. To turn reloading off, remove refreshInterval.
  • If the new domain cannot be loaded, ADS keeps the last valid domain and logs a warning.
  • The file must exist when ADS starts.
  • ADS logs the identity of every domain it loads (Domain with id … was loaded), and each audit log entry has the identity of the domain that made the decision (DomainId).

See Runtime domain updates in the ADS documentation for all settings.

:::warning Replace the file in one step ADS can read the file while it is being written. Write the new domain to a temporary file in the same directory, then rename it to domain.yaml. A rename within one file system replaces the file in one step. The examples below do this. :::

Deliver from Gradle​

A Gradle task that depends on buildAuthzDomain can deliver the domain with any mechanism available in your pipeline. The built file is the output of buildAuthzDomain: use buildAuthzDomain.domainFile instead of the path build/alfa/domain/ads/domain.yaml, so the task follows any configuration changes.

build.gradle
tasks.register('deployDomain') {
dependsOn buildAuthzDomain
doLast {
File domainFile = buildAuthzDomain.domainFile.get().asFile
// TODO: deliver domainFile to the storage ADS reads
}
}

Run ./gradlew test before the delivery task, so that only a tested domain is delivered. Read secrets, such as passwords and tokens, from environment variables. Never write them in build.gradle.

The sections below show common alternatives.

Shared drive​

When the pipeline runner and the ADS hosts mount the same shared drive (for example NFS or SMB), the task copies the domain to the drive:

build.gradle
import java.nio.file.Files
import java.nio.file.StandardCopyOption

tasks.register('deployDomain') {
dependsOn buildAuthzDomain
doLast {
File domainFile = buildAuthzDomain.domainFile.get().asFile
File target = new File(System.getenv('ADS_DOMAIN_DIR'), 'domain.yaml')
File temporary = new File(target.parentFile, 'domain.yaml.tmp')
Files.copy(domainFile.toPath(), temporary.toPath(), StandardCopyOption.REPLACE_EXISTING)
Files.move(temporary.toPath(), target.toPath(), StandardCopyOption.ATOMIC_MOVE)
logger.lifecycle("Delivered the domain to ${target}")
}
}
ADS_DOMAIN_DIR=/mnt/ads-domain/prod ./gradlew deployDomain

Kubernetes volume​

In Kubernetes, the ADS pods mount a PersistentVolumeClaim (PVC) that holds domain.yaml, and ADS_DOMAIN points to the file on it. Use a volume that several pods can mount at the same time (access mode ReadWriteMany), and mount it read-only in the ADS pods. One delivery then updates all ADS pods.

The pipeline cannot write to a PVC directly: it needs a pod that mounts the volume with write access. This can be a small, long-running writer pod, or a Kubernetes Job that the pipeline starts for each delivery. With a writer pod named domain-writer that mounts the volume at /domain:

kubectl cp build/alfa/domain/ads/domain.yaml production/domain-writer:/domain/domain.yaml.tmp
kubectl exec -n production domain-writer -- mv /domain/domain.yaml.tmp /domain/domain.yaml

kubectl cp needs tar in the image of the writer pod.

SCP to a server​

When ADS runs on a server, copy the domain over SSH. The remote path is the file ADS reads:

build.gradle
tasks.register('deployDomain', Exec) {
dependsOn buildAuthzDomain
def host = System.getenv('ADS_HOST')
def domainFile = buildAuthzDomain.domainFile.get().asFile
commandLine 'sh', '-c',
"scp '${domainFile}' ${host}:/opt/ads/domain/domain.yaml.tmp && " +
"ssh ${host} mv /opt/ads/domain/domain.yaml.tmp /opt/ads/domain/domain.yaml"
}
ADS_HOST=deploy@ads-host.example.com ./gradlew deployDomain

Artifactory or another HTTP repository​

A repository such as Artifactory keeps every delivered domain: upload each domain under a version, for example the Git commit, and you can roll back by delivering an earlier version again. Upload with an HTTP PUT:

build.gradle
tasks.register('deployDomain') {
dependsOn buildAuthzDomain
doLast {
def domainFile = buildAuthzDomain.domainFile.get().asFile
def connection = new URL(System.getenv('DOMAIN_URL')).openConnection() as HttpURLConnection
connection.requestMethod = 'PUT'
connection.setRequestProperty('Content-Type', 'application/yaml')
connection.setRequestProperty('Authorization', "Bearer ${System.getenv('DOMAIN_TOKEN')}")
connection.doOutput = true
connection.outputStream.withStream { it.write(domainFile.bytes) }
def code = connection.responseCode
if (code < 200 || code >= 300) {
throw new GradleException("Failed to upload domain.yaml: HTTP $code")
}
logger.lifecycle("Delivered the domain to ${System.getenv('DOMAIN_URL')} (HTTP $code)")
}
}
DOMAIN_URL=https://artifactory.example.com/artifactory/ads-domains/prod/domain.yaml \
DOMAIN_TOKEN=<token> \
./gradlew deployDomain

ADS gets the domain from the repository in one of two ways:

  • ADS loads it directly from the repository URL, as described below.
  • A job in your environment copies the file from the repository to the storage ADS reads, as described in the sections above.

Load the domain from a URL​

domain.path also accepts an HTTP or HTTPS URL. Set ADS_DOMAIN to the URL of the domain, for example ADS_DOMAIN=https://artifactory.example.com/artifactory/ads-domains/prod/domain.yaml. If the repository requires basic authentication, add an authentication section under domain in deployment.yaml:

deployment.yaml
domain:
path: ${ADS_DOMAIN:./domain.yaml}
refreshInterval: 30 seconds
authentication:
username: ${ADS_DOMAIN_USERNAME}
password: ${ADS_DOMAIN_PASSWORD}

Set ADS_DOMAIN_USERNAME and ADS_DOMAIN_PASSWORD in the environment of ADS, for example from a Kubernetes Secret. Use a read-only account: ADS only reads the domain.

  • With refreshInterval, ADS downloads the domain at every interval and loads it when its content has changed, as with a file.
  • ADS does not start if it cannot download the domain, for example because of wrong credentials.
  • If the repository cannot be reached later, or returns an invalid domain, ADS keeps the last valid domain and logs a warning.
  • ADS logs the identity of every domain it loads (Domain with id … was loaded), and each audit log entry has the identity of the domain that made the decision (DomainId).

Instead of basic authentication, ADS can get an OAuth 2 access token (oauth2ClientId), as shown in Configure ADS to load the domain from Authorization Hub, and use a TLS configuration for HTTPS (tlsConfigurationId). See Runtime domain updates in the ADS documentation.

Your own HTTP service​

If the pipeline cannot reach the storage directly, for example because the storage is inside a closed network, you can run your own small HTTP service next to it. The pipeline uploads the domain with the task in the previous section, and the service writes it to the storage that ADS reads, replacing the file in one step.

Such a service is not provided by Axiomatics or APD: you build and operate it as part of your platform, including its authentication and TLS. In Kubernetes it can run as a sidecar container in the ADS pod, sharing an emptyDir volume with ADS. Because a sidecar only updates the ADS instance in its own pod, the pipeline must deliver updates to every pod.

Authorization Hub​

To deliver the domain to Authorization Hub, use the built-in integration instead of a custom task. See Authorization Hub integration.