Skip to main content

Integrations and CD/CI pipelines

Introduction​

The practice of Continuous integration (CI) and continuous delivery (CD) aims at closing the gap between development and operation, increase the speed to production by shifting left (early defect discovery) and automation of the necessary activities.

ABAC with Axiomatics ALFA and policy testing framework is designed with CD/CI in mind and let organizations or teams quickly implement CD/CI pipelines.

Policy testing framework integrates with Authorization Domain Mananger (ADM) and provides the building blocks to implement a CD/CI pipeline of your authorization domains and policies.

ADM integration​

ADM is Axiomatics' content management system for authorization domains (polices and attribute connectors). ADM stores policies and configuration which can be pulled and used by the Axiomatics Decision Service (ADS) (ADS is Axiomatics' PDP). By integrating with ADM you can push, pull, run, test and promote authorization domains.

In ADM, an authorization domain is identified by a namespace and a domain name.

Connect an ADM domain by adding an adm block inside the repositories' section in the build.gradle file:

build.gradle
alfa {
namespace 'JanesNamespace'
repositories {
adm {
environment 'Dev'
host 'https://adm.dev.myorg'
domainName 'JanesDev'
envVariable 'LDAP_PIP_URL', 'http://ldap-dev.myorg.com'
oidcCredentials {
client_id 'ads'
client_secret 'uU12JImeEiA1cfC0q2aNi40DhNkjeK1Ga'
}
}
}
}
PropertyDescription
environmentA free-form string describing the environment or nature of the domain. Suggestions are 'dev, 'qa' or 'prod' for example. This string will be used when creating gradle tasks related to this ADM domain integration.
hostProtocol and hostname of ADM or APS
domainNameThe domain name in ADM
envVariableOptional property. Sets environment variable if you run this domain with ADS from within policy testing framework. Multiple envVariables properties are possible.
client_idThe client id to use for OIDC authentication to ADM
client_secretThe client secret to use for OIDC authentication to ADM

⚠️The ADM namespace is set globally in the alfa section and will be the same for all adm repository entries.

πŸ‘‰ If your ADM has basic authentication instead of OpenID Connect (OIDC), replace the oidcCredentials block with a basic credentials block:

basicCredentials {
username 'user'
password 'password'
}

When you add an ADM repository, a new Gradle task group with tasks will be generated and added. The screenshot below shows the state when an ADM repository with environment name Dev has been added to build.gradle; the task group axiomatics-adm-dev has been created, and it contains 4 tasks: pullFromDev, pushToDev, runAdsWithDev, testDev.

adm-tasks

Certificates​

Any certificates needed to establish TLS communication to ADM or ADS should be added to appropriate truststore (for example the operating system or Java's truststore), or you can specify a custom truststore (JKS or PKCS12 type) with the standard Java system properties:

gradle.properties
# Restart Gradle deamon (./gradlew --stop) after changing these lines, otherwise changes may not be loaded
systemProp.javax.net.ssl.trustStore=mycacerts.jks
systemProp.javax.net.ssl.trustStorePassword=changeit
systemProp.javax.net.ssl.trustStoreType=JKS

or

$ ./gradlew -Djavax.net.ssl.truststore=mycacerts.jks ...

⚠️ NOTE: Gradle deamon should be restarted with ./gradlew --stop) or changes will not be loaded. ⚠️

Task pullFromDev​

Downloads the domain.yaml file from ADM. The domain will be written to build\alfa\axiomatics-adm-dev\domain.yaml.

$ ./gradlew pullFromDev
> Task :pullFromDev
Domain https://adm.dev.myorg/adm/api/namespaces/JanesNamespace/names/JanesDev/domain stored to build\alfa\axiomatics-adm-dev\domain.yaml

Task pushToDev​

Uploads the domain produced by task :buildAuthzDomain (the local domain under src/) to ADM.

⚠️The domain will be pushed as the latest version of this domain and any ADS polling this domain will receive the updated domain.

If you do not want to push the local source domain (for example in a promote scenario), use the copy domain task instead.

$ ./gradlew pushToDev
> Task :pushToDev
Source file is build\alfa\domain\ads\domain.yaml
Domain successfully pushed to https://adm.dev.myorg/adm/api/namespaces/JanesNamespace/names/JanesDev. Server returned new id 087f5c4a-bba2-4cb9-94a3-b9a557d005e0

πŸ‘‰ If tests should also be executed, they have to be invoked explicitly prior to the push, like: $ ./gradlew test pushToDev

Task runAdsWithDev​

Starts an ADS with the domain. ADS will be started on a local random unencrypted HTTP port with basic authentication enabled. Username and password is pdp-user and secret.

πŸ‘‰ Environment variables can be added with envVariable 'KEY', 'VALUE' inside the adm block.

$ ./gradlew runAdsWithDev
> Task :spawnAdsWithDev
Location of domain.yaml is https://adm.dev.myorg/adm/api/namespaces/JanesNamespace/names/JanesDev/domain

> Task :runAdsWithDev
ADS is running on port 52870. To see ADS output run gradle with --info. Stop it with Ctrl-C!

πŸ‘‰ By adding switch --info ADS will be started with DEBUG logging enabled.

Task testDev​

Run the system tests on the domain. The test report will be produced in build/reports/tests/testDev

$ ./gradlew testDev
> Task :spawnAdsWithDev
Location of domain.yaml is https://adm.dev.myorg/adm/api/namespaces/JanesNamespace/names/JanesDev/domain

> Task :testDev
...test results

πŸ‘‰ mainpolicy in alfa section must set property in order to identify system tests.

Copy domains between multiple ADSs​

In a pipeline or DevOps scenario, promotion or copying artifacts between stages can be implemented by adding multiple ads repositories in build.gradle.

The list is ordered and should be defined left (dev) to right (prod), in order for the correct copy tasks to be generated. If you have 3 environments (development, quality assurance and production) the ADM repositories in build.gradle should be defined as follows:

build.gradle
alfa {
namespace 'JanesNamespace'
repositories {
adm {
environment 'Dev'
...
}
adm {
environment 'QA'
...
}
adm {
environment 'Prod'
...
}

πŸ‘‰ Note that the different environments not necessarily needs to be in different ADM servers. It depends on the isolation between environments that you have.

The following tasks will be added in respectively task group: promoteFromDevToQA, promoteFromDevToProd, promoteFromQAToProd.

Execution of task promoteFromDevToQA will copy the domain specified in the Dev environment to the domain specified in the QA environment:

> Task :pullFromDev
Domain https://adm.dev.myorg/adm/api/namespaces/JanesNamespace/names/JanesDev/domain stored to build\alfa\axiomatics-adm-dev\domain.yaml

> Task :promoteFromDevToProd
Source file is build\alfa\axiomatics-adm-dev\domain.yaml
Domain successfully pushed to https://adm.myorg/adm/api/namespaces/JanesNamespace/names/production/domain. Server returned new id 03e830ad-8422-44ff-8775-a02c24b92800

BUILD SUCCESSFUL in 6s


CD/CI pipeline example​

Acme is an enterprise with an ABAC domain consisting of 20 attributes, 100 ALFA policies and 10 attribute connectors. The domain has been developed such as every policy and every attribute connector has unit and integration tests written. There are also acceptance tests defined by business owners and business analysts.

Acme has 2 pre-production environments (dev and qa) and one production environment. In dev only the ABAC team has access. In QA environment applications and PEPs are connected to ADS.

Whenever a change is required to the authorization domain (may it be changing requirements or new use-case), a developer always updates or writes the test first. After that necessary changes to polices and attribute connectors are implemented, in such wat that the tests pass.

The developer makes sure all tests pass,by invoking ./gradlew test from her laptop. Any new or changed environment variables for the environments are also implemented.

When the developer is happy, she makes a commit and pushes the code to the VCS ( Git, Bitbucket or Azure DevOps e.g.). The push triggers the pipeline (Jenkins or Azure DevOps e.g.) to be executed. The pipeline checks out the new code and invokes the following tasks on the build server to test both the authorization domains and ADS in all environments.

./gradlew test
./gradlew pushToDev
./gradlew testDev
# pause until ADS in dev has picked up new domain from ADM
./gradlew testAdsInDev
./gradlew promoteFromDevToQA
./gradlew testQA
# pause until ADS in QA has picked up new domain from ADM
./gradlew testAdsInQA
# manual input: Proceed promote to production?
./gradlew promoteFromQAToProd
./gradlew testProd

πŸ‘‰ The above pipeline may be split to one or more distinct pipelines depending on how your build environments look like.

⚠️Environment variables in all environments should be maintained as necessary, meaning updating the tasks in build.gradle but also the deployment descriptors of all ADS, which exists outside this project for example in Kubernetes.

The full build.gradle for this example could look like:

build.gradle
plugins {
id 'alfa-testing-framework'
}

version = '1.0'
group = 'com.acme.abac'

def KEY_ADM_CLIENTID = 'ADM_USER'
def KEY_ADM_SECRET = 'ADM_PASSWORD'
def KEY_PIP_LDAP_URL = 'PIP_LDAP_URL'
def KEY_PIP_LDAP_USER = 'PIP_LDAP_USER'
def KEY_PIP_LDAP_PASSWORD = 'PIP_LDAP_PASSWORD'
def KEY_ADS_USER = 'ADS_USER'
def KEY_ADS_PASSWORD = 'ADS_PASSWORD'

alfa {
namespace 'Acme'
mainpolicy "acme.Main"

licenseFile 'license/axiomatics_PDP.license'

repositories {
adm {
environment 'Dev'
host 'https://adm.dev.acme.com'
domainName 'dev'

envVariable KEY_PIP_LDAP_URL, 'https://ldap.dev.acme.com'
envVariable KEY_PIP_LDAP_USER, 'user'
envVariable KEY_PIP_LDAP_PASSWORD, 'password'

oidcCredentials {
client_id 'devops'
client_secret '4afgcd8JImeEiA1mC0q2aNi40DhUjeS98a'
}
}
adm {
environment 'QA'
host 'https://adm.qa.acme.com'
domainName 'qa'

envVariable KEY_PIP_LDAP_URL, 'https://ldap.qa.acme.com'
envVariable KEY_PIP_LDAP_USER, providers.environmentVariable(KEY_PIP_LDAP_USER).get()
envVariable KEY_PIP_LDAP_PASSWORD, providers.environmentVariable(KEY_PIP_LDAP_PASSWORD).get()

oidcCredentials {
client_id providers.environmentVariable(KEY_ADM_CLIENTID).get()
client_secret providers.environmentVariable(KEY_ADM_SECRET).get()
}
}
adm {
environment 'Prod'
host 'https://adm.acme.com'
domainName 'prod'

envVariable KEY_PIP_LDAP_URL, 'https://ldap.qa.acme.com'
envVariable KEY_PIP_LDAP_USER, providers.environmentVariable(KEY_PIP_LDAP_USER).get()
envVariable KEY_PIP_LDAP_PASSWORD, providers.environmentVariable(KEY_PIP_LDAP_PASSWORD).get()

oidcCredentials {
client_id providers.environmentVariable(KEY_ADM_CLIENTID).get()
client_secret providers.environmentVariable(KEY_ADM_SECRET).get()
}
}
}
}

test {
environment KEY_PIP_LDAP_URL, 'http://ldap.dev.acme.com'
environment KEY_PIP_LDAP_USER, 'user'
environment KEY_PIP_LDAP_PASSWORD, 'password'
}

task testAdsInDev(type: Test) {
group "axiomatics-adm-dev"
environment "ALFA_TEST_REMOTE_URL", "http://ads.dev.acme.com:8081/authorize"
environment "ALFA_TEST_REMOTE_USER", "pdp-user"
environment "ALFA_TEST_REMOTE_PASSWORD", "secret"
}

task testAdsInQa(type: Test) {
group "axiomatics-adm-qa"
environment "ALFA_TEST_REMOTE_URL", "https://ads.qa.acme.com:8081/authorize"
environment "ALFA_TEST_REMOTE_USER", providers.environmentVariable(KEY_ADS_USER).get()
environment "ALFA_TEST_REMOTE_PASSWORD", providers.environmentVariable(KEY_ADS_PASSWORD).get()
}

Customizations​

Policy testing framework and Gradle are very flexible and customizable. If you have other use cases you can configure or program new tasks to accomondate your needs. Do not hesitate to contact Axiomatics Support if you have any questions.