Develocity Provenance Governor


Develocity Provenance Governor is an enterprise solution designed to help automate governance, compliance, and risk for the software supply chain. It enables organizations to define, manage, and enforce provenance and attestation policies for software packages, ensuring that only trusted and compliant artifacts are promoted and consumed across environments.

1. How it works

Leveraging the build data in Develocity, Develocity Provenance Governor collects, verifies, and evaluates provenance data and attestations. The product offers detailed provenance attestations, declarative domain-aware policies, and automated evaluation, empowering teams to:

  • Enforce organizational and regulatory requirements for software provenance

  • Prevent the use of untrusted or non-compliant artifacts

  • Gain visibility into the origin and integrity of software components

  • Streamline compliance reporting and incident response

1.1. Key Concepts

Below are the terms and concepts you need to understand to use Develocity Provenance Governor:

  • Attestations are signed statements about the origin (or provenance) of a software package, including details about its build process, dependencies, and other metadata. Attestations can be used to verify the integrity and authenticity of software packages.

  • Policies define rules and conditions that software packages must adhere to. You can use policies to prevent non-compliant or untrusted packages from being released, deployed, or used.

  • Policy scans evaluate packages against defined policies and report on the package’s level of compliance.

1.2. Components

The following diagram illustrates the main components of Develocity Provenance Governor and how they interact with each other and with external systems.

This diagram illustrates the main components of Develocity Provenance Governor and how they interact with each other and with external systems.

1.3. Flow

Below is a typical flow for using Develocity Provenance Governor in a software supply chain:

  1. A build runs on a Continuous Integration (CI) system and:

    1. Produces a software package (for example, a JAR file).

    2. Publishes a Build Scan to Develocity (Build Scans capture detailed information about the build process).

    3. Uploads the software package to a package repository (for example, JFrog Artifactory).

    4. Calls Develocity Provenance Governor to generate and publish attestations for the software package.

  2. Develocity Provenance Governor generates attestations based on Build Scan data from Develocity that was collected while building the software package.

    1. After an attestation is generated, Develocity Provenance Governor signs the attestation using a private key.

    2. The signed attestation is published to an attestation store. Develocity Provenance Governor supports publishing attestations to JFrog Artifactory.

  3. A later CI or Continuous Deployment (CD) job calls Develocity Provenance Governor to perform a policy scan on the software package.

    1. Develocity Provenance Governor performs a policy scan to evaluate policies against the software package to determine if it is compliant with organization rules and requirements. Develocity Provenance Governor uses the previously published attestations to evaluate policies.

    2. The result of the policy scan is returned to the calling job.

    3. Based on the results of the policy scan, the job can decide to proceed or fail.

Once published, attestations generated by Develocity Provenance Governor can also be used with JFrog Evidence features and functionality.

2. Getting set up

Develocity Provenance Governor is delivered as a containerized application designed for Kubernetes deployment. While the application is cloud-native by design, proper setup requires careful configuration of prerequisites, credentials, and policies.

2.1. Prerequisites

Before deploying Develocity Provenance Governor, ensure you have the following prerequisites. These are organized in a logical order that matches the typical installation workflow: infrastructure setup, external integrations, and security configuration.

Recommended Setup Order:

Infrastructure (set up first):

  1. Develocity License - Required for registry authentication

  2. Kubernetes Cluster - Deployment platform

  3. Hostname - DNS configuration for ingress

  4. TLS Certificate - SSL/TLS for secure access

External Integrations (configure before deployment):

  1. Develocity Instance - Source of build data

  2. Artifactory with Evidence Management - Attestation storage (Enterprise+ required)

Security (prepare before first use):

  1. Signing Key Pair - For attestation signatures

Planning:

  1. Cluster Resources - Review resource requirements

2.1.1. Develocity License

Develocity Provenance Governor uses your existing Develocity license - no separate license is required. Any valid Develocity license works; no special entitlements are needed. If you are already a Develocity customer, you can use your existing license. If you do not have a Develocity license, contact Gradle’s sales team and mention that you need access for "Develocity Provenance Governor".

2.1.2. Kubernetes Cluster

Develocity Provenance Governor is designed to run in a Kubernetes environment. You will need to install it into a Kubernetes cluster. The Kubernetes cluster must have an Ingress Controller.

Develocity Provenance Governor can be deployed in environments other than Kubernetes. If you need to run Develocity Provenance Governor outside of Kubernetes please open a support ticket and describe your deployment requirements.

2.1.3. Cluster Resources

Plan for these resource requirements: By default, the combined Kubernetes resource requests are 2 CPUs and 1 GiB memory per replica. These values are configurable via Kubernetes Deployment values, but lower values may impact performance.

2.1.4. JFrog Artifactory with Evidence Management

Artifactory Enterprise+ license (version 7.104.2 or later) is required. The Evidence Management feature is mandatory for Develocity Provenance Governor operation. Without Artifactory configured with this feature, Develocity Provenance Governor cannot store or retrieve attestations.

Develocity Provenance Governor requires Artifactory as the attestation store. All attestations are stored in and retrieved from Artifactory, making it a critical component of your deployment.

Requirements:

  • Artifactory version 7.104.2 or later

  • Enterprise+ license

  • Evidence Management feature enabled with support for external evidence

  • Network access from the Develocity Provenance Governor deployment to the Artifactory instance

  • Artifactory Access Token or Identity Token with the following permissions:

    • Read - to retrieve attestations

    • Annotate - to attach evidence metadata to artifacts

2.1.5. Develocity Instance

Develocity Provenance Governor uses Develocity Build Scan data to generate attestations. It requires network access to a Develocity instance.

Develocity Provenance Governor requires a valid Develocity access key for a user with the “Access build data via the API” permission. If the Develocity instance is using Project-level Access Control, Develocity Provenance Governor will only be able to publish attestations for Build Scan data in projects that the user has access to.

The minimum version of Develocity that Develocity Provenance Governor supports is 2023.3. Develocity Provenance Governor supports publishing attestations from Build Scan data from the following build tools:

  • Gradle, with Develocity plugin version 1.16 or later.

    • JVM attestations for Gradle toolchains are only supported for plugin 3.11 or later.

  • Maven, with Develocity extension version 1.4 or later.

We recommend using the latest available version of Develocity and its build tool integrations for the best experience.

2.1.6. Signing Key Pair

Develocity Provenance Governor cryptographically signs all attestations to ensure authenticity and prevent tampering. You need to generate a signing key pair before deployment. The private key remains with Develocity Provenance Governor and is used to sign attestations. The public key must be registered in your Artifactory instance to allow verification of signed attestations.

Supported Key Types:

We support Ed25519, RSA, and Elliptic Curve keys. Ed25519 is recommended because it provides strong security (equivalent to 128-bit security) with significantly smaller keys and faster operations than RSA or ECDSA.

Generating a Key Pair:

Develocity Provenance Governor needs to be configured with a signing key pair in PEM format. You can generate an Ed25519 key pair using OpenSSL:

openssl genpkey -algorithm Ed25519 -out ./key-pair.private-pem
openssl pkey -in ./key-pair.private-pem -pubout -out ./key-pair.public-pem

Keep the private key secure. You’ll configure it as a Kubernetes secret during deployment. The public key will be registered in Artifactory to verify attestation signatures.

2.1.7. Hostname

Develocity Provenance Governor needs a hostname. It will be used in its Ingress. Ensure that the hostname’s DNS is configured to work with the Kubernetes cluster’s Ingress Controller.

2.1.8. TLS Certificate

You’ll need a TLS (Transport Layer Security) certificate for your hostname to enable SSL (Secure Sockets Layer). While SSL/TLS is technically optional, it is strongly recommended for production environments.

Options include:

  • Using an existing organizational certificate

  • Generating one with cert-manager and Let’s Encrypt (see deployment section for details)

  • Using a self-signed certificate for testing (not recommended for production)

2.2. Deployment

Develocity Provenance Governor is provided as a container image that runs on a Kubernetes cluster (recommended) or a local Docker/Kubernetes environment. Make sure you have the prerequisites detailed in Prerequisites before proceeding.

2.2.1. Audience

This guide is written for a Platform / Infrastructure Engineer. It assumes:

  • Basic familiarity with Kubernetes (namespaces, secrets, configmaps, deployments, ingress)

  • Ability to obtain a Develocity license file (develocity.license)

  • (Optional) Ability to obtain access tokens/keys for Develocity and Artifactory instances

  • Access to a DNS domain (optional, for public HTTPS ingress)

2.2.2. Quick Start

Want to get started quickly with a local k3d cluster and basic HTTP ingress?

See the Quick Start Guide for condensed setup instructions.

The quick start will guide you through:

  • Creating a local k3d cluster

  • Deploying Develocity Provenance Governor with basic HTTP ingress

  • Verifying the deployment

After completing the quick start, return to the sections below to configure additional features like authentication, integrations, and policies.

2.2.3. Deployment Flow Overview

The recommended order is:

  1. Choose / create a Kubernetes cluster (local k3d or existing)

  2. Create namespace + required secrets (registry + license)

  3. Configure Ingress (HTTP, then HTTPS + cert-manager) - Optional

  4. Deploy the core workload (ServiceAccount, Services, Deployment, NetworkPolicy)

  5. Verify rollout & logs

  6. Create placeholder Secrets/ConfigMaps for dynamic configuration (secrets, properties, policies)

  7. Add integrations (Develocity instances, Artifactory instances)

  8. Add signing keys

  9. Enable authentication (Basic and/or OIDC)

  10. Define policies (AccessControl manifests)

  11. Roll out config changes as needed

  12. Troubleshoot rollout issues

Estimated setup time:

  • Local k3d (Quick Start): 15-30 minutes

  • Production setup with all integrations: 2-4 hours (depending on external service coordination and certificate setup)

2.2.4. Select Kubernetes Distribution

Before proceeding ensure access to a Kubernetes cluster has been provided, otherwise a local k3d cluster is also supported.

k3d
Install k3d
brew install k3d
Create k3d cluster (port 80 exposed for ingress)
k3d cluster create --port "80:80@loadbalancer"

2.2.5. Prepare the Kubernetes Namespace and Required Secrets

You must create a Kubernetes namespace and the secrets needed for the application to start.

Create Kubernetes Namespace
Namespace that the Develocity Provenance Governor will be deployed to.
kubectl create namespace develocity-provenance-governor
Docker Registry Secret (Required)

Authenticate to registry.gradle.com to pull the product image.

kubectl create secret docker-registry registry-secret \
  --namespace develocity-provenance-governor \
  --docker-server=https://registry.gradle.com \
  --docker-username=user \
  --docker-password=$(cat ./develocity.license)
Develocity License Secret (Required)

A Develocity license is required for the deployment to be successful.

kubectl create secret generic license \
  --namespace develocity-provenance-governor \
  --from-file=develocity.license=./develocity.license

2.2.6. Ingress (Optional)

No default ingress is provided. Choose what aligns with organizational standards. Below examples cover:

  • Local development (nip.io hostname)

  • HTTPS with cert-manager & Let’s Encrypt

Create Ingress Manifest
kubectl --namespace develocity-provenance-governor apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api.127.0.0.1.nip.io
spec:
  rules:
    - host: api.127.0.0.1.nip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-http
                port:
                  number: 80
EOF
Verify Ingress Resource
kubectl get ingress api.127.0.0.1.nip.io --namespace develocity-provenance-governor
Expected Response
NAME   CLASS     HOSTS                  ADDRESS      PORTS   AGE
http   traefik   api.127.0.0.1.nip.io   172.18.0.3   80      15s
Verify Route
curl api.127.0.0.1.nip.io -i
Expected Response
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 0
Expires: 0
Pragma: no-cache
Referrer-Policy: no-referrer
Www-Authenticate: Basic realm="Realm"
...
Ingress with cert-manager

Alternative, ingress manifest using cert-manager, nginx, and signed certificate from Let’s Encrypt. The ingress resource, can be used to expose the api service outside the cluster. Modify the provided manifest to reflect the domain that you control paying close attention to tls[0].hosts[0] and rules[0].host.

kubectl --namespace develocity-provenance-governor apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: https
spec:
  tls:
  - hosts:
    - provenance-governor.example.com
    secretName: https-api-tls
  rules:
  - host: provenance-governor.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-http
            port:
              number: 80
EOF

At this point, the ingress controller might use a self-signed certificate provided as a default.

Verify Ingress Resource
kubectl get ingress https --namespace develocity-provenance-governor
Expected Response
NAME   CLASS     HOSTS                  ADDRESS      PORTS   AGE
https   traefik   provenance-governor.example.com   xxx.xxx.xx.x   80, 443      15s

If not already deployed, install cert-manager

Create an Issuer
kubectl --namespace develocity-provenance-governor create --edit -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: lets-encrypt
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: user@example.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: lets-encrypt
    # Enable the HTTP-01 challenge provider
    solvers:
      - http01:
          ingress:
            ingressClassName: nginx
EOF
This will create a certificate issuer using the Production Instance of Let’s Encrypt. Edit accordingly based on desired certificate issuer implementation. If using Let’s Encrypt, ensure the ingress is publicly accessible and can handle a http01 challenge provider request to complete certificate issuance. Also, be sure to update the email address.
Verify the Issuer
kubectl describe issuer lets-encrypt \
  --namespace develocity-provenance-governor

Detailed issuer information should be returned along with Status.Status: True and Status.Type: Ready.

Edit the https ingress resource to use the lets-encrypt issuer instead of the self signed certificate issuer.

Edit ingress resource
kubectl edit ingress https \
  --namespace develocity-provenance-governor
Add cert-manager.io/issuer: lets-encrypt to annotations:
metadata:
  annotations:
    cert-manager.io/issuer: lets-encrypt
Verify Certificate Issued
$ kubectl get certificate \
  --namespace develocity-provenance-governor

NAME            READY   SECRET          AGE
https-api-tls   True    https-api-tls   2m31s
Verify Https Endpoint

$ curl https://provenance-governor.example.com -i HTTP/2 401 …​

In this example, the request to https://provenance-governor.example.com returns a 401 Unauthorized status code because no credentials were provided. This confirms that the Ingress is correctly routing traffic to the service and that the service is up and running.

To verify the service health:

curl https://provenance-governor.example.com/readyz -i

No SSL negotiation exceptions should be thrown and a 401 should be returned.

2.2.7. Deploy Core Components

Apply the workload manifests. (See Quick Start)

Download the manifest.yaml file.

kubectl --namespace develocity-provenance-governor apply -f manifest.yaml
Verify Rollout
kubectl rollout status deployment/api \
  --timeout=90s \
  --namespace develocity-provenance-governor

Expected output: deployment "api" successfully rolled out. If rollout fails:

  • Verify registry-secret credentials and image digest/tag

  • Verify license secret presence and correct file content

Verify Logs
kubectl logs deployment/api \
  --namespace develocity-provenance-governor | grep -i license
Sample lines
Develocity license enabled, with license [...]
Started ProvenanceGovernor in ...

The Deployment mounts (optionally) these resources:

Resource

Kind

Purpose

Required

Mounted Path

license

Secret

Develocity product license

Yes

/workspace/config/license

secrets

Secret

Sensitive values (access tokens, private keys)

No

/workspace/config/secrets

properties

ConfigMap

Non-sensitive configuration (URIs, public keys)

No

/workspace/config/properties

policies

ConfigMap

Policy manifests

No

/workspace/config/policies

Create empty resources to prepare for configuration:

kubectl create secret generic secrets --namespace develocity-provenance-governor || true
kubectl create configmap properties --namespace develocity-provenance-governor || true
kubectl create configmap policies --namespace develocity-provenance-governor || true

2.2.9. Configure Develocity Instances (Optional)

Use one or more Develocity instances as attestation data sources.

Key patterns:

  • Secret: develocity.instances.<NAME>.access-key

  • ConfigMap: develocity.instances.<NAME>.uri

<NAME> must be alphanumeric plus dashes or underscores.

Edit Secret
kubectl edit secret secrets --namespace develocity-provenance-governor

Add access key (use stringData: when adding new values):

stringData:
  develocity.instances.MY_INSTANCE.access-key: my-access-key-for-develocity
Edit ConfigMap
kubectl edit configmap properties --namespace develocity-provenance-governor

Add URI:

data:
  develocity.instances.MY_INSTANCE.uri: https://develocity.example.com

Rollout & Verify:

kubectl rollout restart deployment/api --namespace develocity-provenance-governor
kubectl rollout status deployment/api --namespace develocity-provenance-governor --timeout=90s
kubectl logs deployment/api --namespace develocity-provenance-governor | grep -i "Develocity support enabled"

Expected log snippet:

Develocity support enabled, for instance [MY_INSTANCE:https://develocity.example.com]

Troubleshooting: Repeated Retrying [n/10] request …​ indicates connectivity or credential issues.

2.2.10. Configure Artifactory Instances (Optional)

Used to publish attestation data.

Key patterns:

  • Secret: artifactory.instances.<NAME>.access-token (token preferred over legacy key)

  • ConfigMap: artifactory.instances.<NAME>.uri

Edit Secret
kubectl edit secret secrets --namespace develocity-provenance-governor

Add token:

stringData:
  artifactory.instances.MY_ARTIFACTORY.access-token: my-access-token-for-artifactory
Edit ConfigMap
kubectl edit configmap properties --namespace develocity-provenance-governor

Add URI:

data:
  artifactory.instances.MY_ARTIFACTORY.uri: https://artifactory.example.com

Rollout & Verify:

kubectl rollout restart deployment/api --namespace develocity-provenance-governor
kubectl rollout status deployment/api --namespace develocity-provenance-governor --timeout=90s
kubectl logs deployment/api --namespace develocity-provenance-governor | grep -i "Artifactory support enabled"

Expected log snippet:

Artifactory support enabled, for instance [MY_ARTIFACTORY:https://artifactory.example.com]

Attestations are wrapped in a Dead Simple Signing Envelope (DSSE) signed by a key pair.

Required key property patterns:

  • Secret (private key, base64 PEM): signing.key.<FRIENDLY_KEY_NAME>.private-pem

  • ConfigMap (public key PEM block): signing.key.<FRIENDLY_KEY_NAME>.public-pem

Choose a naming convention like <ORG_OR_PRODUCT>_YYYY-MM-DD for rotation clarity.

Create Ed25519 Key Pair

Recommended (128-bit security, small key sizes/signatures).

openssl genpkey \
  -algorithm Ed25519 \
  -out private-key.pem
Use OpenSSL to create a Public Key based on the Private Key
openssl pkey \
  -in private-key.pem \
  -pubout \
  -out public-key.pem
Edit Secrets
kubectl edit secret secrets \
  --namespace develocity-provenance-governor

Base64 and add private key (Secret):

base64 -i private-key.pem | pbcopy
kubectl edit secret secrets --namespace develocity-provenance-governor

Paste under data: (Kubernetes will not modify pasted base64):

data:
  signing.key.FRIENDLY_KEY_NAME.private-pem: <PASTE_BASE64_PRIVATE_KEY>
Add public key (ConfigMap):
cat public-key.pem | sed 's/^/    /' | pbcopy
kubectl edit configmap properties --namespace develocity-provenance-governor

Paste under data::

data:
  signing.key.FRIENDLY_KEY_NAME.public-pem: |
# PASTE THE PUBLIC KEY BLOCK HERE FROM CLIPBOARD
    -----BEGIN PUBLIC KEY-----
    ...
    -----END PUBLIC KEY-----

Rollout & Verify:

kubectl rollout restart deployment/api --namespace develocity-provenance-governor
kubectl logs deployment/api --namespace develocity-provenance-governor | grep -i "Signature support enabled"

Expected log:

Signature support enabled, for key pair [name:FRIENDLY_KEY_NAME:keyid:XXXXXX:signing-algorithm:ed25519]

Key ID is a 6-character abbreviation of the SHA-256 digest of the public key.

Other Supported Algorithms

Elliptic Curve (ECDSA) and RSA are also supported. Substitute commands below; follow the same secret/configmap procedure.

Elliptic Curve (prime256v1)
openssl ecparam -name prime256v1 -genkey -noout -out ec_private.key
openssl pkcs8 -topk8 -inform PEM -in ec_private.key -outform PEM -nocrypt -out private-key.pem
openssl ec -in ec_private.key -pubout -out public-key.pem

Expected log algorithm: SHA256withECDSA.

RSA (2048)
openssl genrsa -out private-key.pem 2048
openssl rsa -in private-key.pem -pubout -out public-key.pem

Expected log algorithm: SHA256withRSA.

2.2.12. Authentication (Optional)

Supported schemes:

Enable Basic Authentication

Add identities under:

  • Secret key pattern: basic.identities.<NAME>

<NAME> is a human-friendly identifier. No default credentials exist.

kubectl edit secret secrets --namespace develocity-provenance-governor
Add a new Basic Identity
stringData:
  basic.identities.some-user: "{noop}some-pass"

Recommendation: Replace {noop} with {bcrypt} and supply a bcrypt-hashed password.

Rollout Deployment
kubectl rollout restart deployment/api \
  --namespace develocity-provenance-governor
Verify Basic Authentication Enabled
kubectl logs deployment/api \
  --namespace develocity-provenance-governor |
  grep -i "Basic Identity support"
Expected Log Line
Basic Identity support enabled, for identity [some-user]
Enable OIDC Authentication

OIDC Providers are dynamically discovered based on token issuers specified in policies. See the Policies section for examples.

2.2.13. Policies (Optional)

Policies are YAML documents stored inside the policies ConfigMap. Each key corresponds to a file-style name ending with .yaml.

Access Control Policies

Define who can do what on which resources.

identityMatchingStrategy supported:

  • withBasicIdentity – must match a key under basic.identities.<NAME> in secrets

  • withOidc – relies on issuer discovery; ensure network accessibility

canPerform supported actions: * publish-attestations - publish DSSE-wrapped attestations to an Attestation Store (e.g., Artifactory) * publish-policy-scans - publish policy scan results to an Attestation Store

withResources supported patterns:

  • pkg:pkg-type/pkg-namespace/pkg-name@pkg-vesion - specific Package URL pattern to match or use wildcards:

    • pkg:maven/* - all Maven packages

    • pkg:oci/* - all OCI packages (container images)

    • pkg:maven/org.example/@ - all Maven packages in org.example namespace and any version

    • pkg:maven/org.example/my-app@1.0.0 - specific Maven package

    • pkg:oci/image-name@v1* - specific OCI image with wildcard version

  • *:REPLACE_WITH_DV_INSTANCE_NAME/* - allow to read build scans from the Develocity Instance to source data for attestations.

  • *:REPLACE_WITH_ARTIFACTORY_INSTANCE_NAME/* - allow to publish attestations to the Artifactory Instance. Replace * with the repository name in artifactory to restrict access further.

  • *:*/* - allows authenticated identity access to all resources (not recommended for production use)

Basic Identity Policy Example
kubectl edit configmap policies --namespace develocity-provenance-governor
Add basic-identity-some-user-full-access.yaml policy
data:
  basic-identity-some-user-full-access.yaml: |
    apiVersion: policy.gradle.com/v1
    kind: AccessControl
    metadata:
      name: basic-identity-some-user-full-access
    spec:
      identityMatchingStrategy:
        withBasicIdentity:
          - withName: "some-user"
      canPerform:
        - publish-attestations
        - publish-policy-scans
      withResources:
        - "*:*/*"
OIDC Policy Example (GitHub Actions)
kubectl edit configmap policies --namespace develocity-provenance-governor
Add oidc-github-action-full-access.yaml policy
data:
  oidc-github-action-full-access.yaml: |
    apiVersion: policy.gradle.com/v1
    kind: AccessControl
    metadata:
      name: oidc-github-action-full-access
    spec:
      identityMatchingStrategy:
        withOidc:
          - fromIssuerUri: https://token.actions.githubusercontent.com
            withClaims:
              job_workflow_ref: org/automation-repo/.github/workflows/build-image.yml@*
      canPerform:
        - publish-attestations
        - publish-policy-scans
      withResources:
        - "*:*/*"
Public Key Verification Policy Example
kubectl edit configmap policies --namespace develocity-provenance-governor
Add public-key-verification-policy.yaml policy
data:
    public-key-verification-policy.yaml: |
        apiVersion: policy.gradle.com/v1
        kind: TrustedPublicKeys
        metadata:
          name: public-keys
        spec:
          resultsLabels: []
          description: Public keys trusted for attestations.
          remediation: Update the list of trusted public keys to ensure only verified attestations are accepted.
          keys:
            DEPLOYMENT_KEY_2025_10_01:
              pem: |
                -----BEGIN PUBLIC KEY-----
                ....
                -----END PUBLIC KEY-----

Replace DEPLOYMENT_KEY_2025_10_01 and the PEM block with your actual trusted public keys, for example the ones used to sign attestations.

Rollout Deployment
kubectl rollout restart deployment/api \
    --namespace develocity-provenance-governor
kubectl rollout status deployment/api \
    --namespace develocity-provenance-governor \
    --timeout=90s
Verify Policies Loaded
kubectl logs deployment/api \
    --namespace develocity-provenance-governor |
    grep -i "Loading Policy Resource from file"
Expected Log Lines
Loading Policy Resource from file [/workspace/config/policies/public-key-verification-policy.yaml]

2.2.14. Troubleshooting Deployment Rollouts

Common rollout hang:

Waiting for deployment "api" rollout to finish: 1 old replicas are pending termination...
error: timed out waiting for the condition

Inspect pods:

kubectl get pods --namespace develocity-provenance-governor

Identify failing pod (e.g., CrashLoopBackOff) and fetch logs:

kubectl logs api-<POD_ID> --namespace develocity-provenance-governor

Example configuration error:

***************************
APPLICATION FAILED TO START
***************************

Failed to bind properties under 'artifactory.instances.INSTANCE_NAME_GOES_HERE' ... Reason: java.lang.IllegalArgumentException: uri must not be null

Remediation:

  • Missing URI – edit properties ConfigMap key artifactory.instances.<NAME>.uri

  • Missing token – edit secrets key artifactory.instances.<NAME>.access-token

After fixing, restart:

kubectl rollout restart deployment/api --namespace develocity-provenance-governor
kubectl rollout status deployment/api --namespace develocity-provenance-governor --timeout=90s

2.2.15. Verification and Next Steps

After completing the deployment, verify that Develocity Provenance Governor is running correctly:

  1. Check Pod Status

    kubectl get pods -n develocity-provenance-governor

    You should see the api pod in Running state with 1/1 ready.

  2. Check Application Logs

    kubectl logs deployment/api -n develocity-provenance-governor --tail=50

    Look for messages indicating: License loaded successfully Integrations enabled (e.g., "Develocity support enabled", "Artifactory support enabled") Policies loaded (if configured) No error messages

  3. Test API Accessibility

    curl -i http://your-provenance-governor-hostname

    You should receive a 401 Unauthorized response, which confirms the application is running and authentication is required.

    The actuator endpoints (health, readiness, liveness) are exposed on port 9090 and not accessible through the public ingress. Develocity Provenance Governor also exposes /livez and /readyz endpoints on the main application port for health checking when Kubernetes probes are enabled.

  4. Verify Integration Configuration

    Check that your configured integrations were loaded:

    kubectl logs deployment/api -n develocity-provenance-governor | grep "support enabled"

Successfully deployed? If all verification steps passed and you’re ready to start using Develocity Provenance Governor, proceed to Section 3: Using Develocity Provenance Governor to learn how to publish attestations and evaluate policies.

What to Do Next:

If you encounter issues, consult the Troubleshooting section.

2.2.16. Summary Cheat Sheet

Integration

Secret Key Pattern

ConfigMap Key Pattern

Mandatory

Notes

License

(file) develocity.license

n/a

Yes

Provided via license secret

Registry Auth

.dockerconfigjson

n/a

Yes

JSON auth file for image pulls

Develocity Instance

develocity.instances.<NAME>.access-key

develocity.instances.<NAME>.uri

No

Multiple supported

Artifactory Instance

artifactory.instances.<NAME>.access-token

artifactory.instances.<NAME>.uri

No

Token preferred

Signing Key (Private)

signing.key.<KEY>.private-pem

n/a

No

Base64 PEM

Signing Key (Public)

n/a

signing.key.<KEY>.public-pem

No

PEM block

Basic Identity

basic.identities.<NAME>

n/a

No

{bcrypt} recommended

Policies

n/a

<policy-name>.yaml in policies ConfigMap

No

AccessControl docs

2.3. Application Configuration

Configuration for Develocity Provenance Governor uses Kubernetes-native resources that are mounted into the container at startup:

  • ConfigMap 'properties' - Non-sensitive settings (URIs, public keys)

  • Secret 'secrets' - Sensitive data (access keys, tokens, private keys)

  • ConfigMap 'policies' - YAML policy definitions

Changes to these resources take effect after restarting the deployment.

2.3.1. Configuration Overview

Configure Develocity Provenance Governor in the following order:

Component Description Required

Develocity Connection

Connect to Develocity instance(s) to retrieve build scan data for attestation generation

✓ Required

Attestation Storage

Configure at least one storage backend: Artifactory or S3

✓ Required (choose one or both)

Signing Keys

Generate and configure signing keys for cryptographically signing attestations

Recommended

Access Control

Define who can access which resources via Access Control policies

✓ Required

Policies

Define policy scans and validation rules for evaluating packages

Optional (required for policy evaluation)

Minimum viable configuration requires:

  1. At least one Develocity instance connection

  2. At least one attestation storage backend (Artifactory and/or S3)

  3. Access Control policies to grant API access

Recommended configuration also includes:

  1. Signing keys for attestation signatures

  2. Policy definitions for policy scan evaluation

2.3.2. How to Configure Properties

Develocity Provenance Governor reads configuration from two Kubernetes resources:

  • ConfigMap properties - Non-sensitive configuration (URIs, options, public keys)

  • Secret secrets - Sensitive configuration (access tokens, private signing keys)

There are two ways to structure configuration in these resources:

Each property is a separate key in the ConfigMap or Secret:

Example ConfigMap with individual properties
apiVersion: v1
kind: ConfigMap
metadata:
  name: properties
  namespace: develocity-provenance-governor
data:
  develocity.instances.prod.uri: "https://develocity.example.com"
  s3.instances.prod-bucket.region: "us-east-1"
  s3.instances.prod-bucket.bucket-name: "dpg-attestations"
Example Secret with individual properties
apiVersion: v1
kind: Secret
metadata:
  name: secrets
  namespace: develocity-provenance-governor
type: Opaque
stringData:
  develocity.instances.prod.access-key: "your-access-key-here"
  artifactory.instances.main.access-token: "your-token-here"

All properties in a single YAML file as one key:

Example ConfigMap with application.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: properties
  namespace: develocity-provenance-governor
data:
  application.yml: |
    develocity:
      instances:
        prod:
          uri: "https://develocity.example.com"
    s3:
      instances:
        prod-bucket:
          region: "us-east-1"
          bucket-name: "dpg-attestations"
Example Secret with application.yml
apiVersion: v1
kind: Secret
metadata:
  name: secrets
  namespace: develocity-provenance-governor
type: Opaque
stringData:
  application.yml: |
    develocity:
      instances:
        prod:
          access-key: "your-access-key-here"
    artifactory:
      instances:
        main:
          access-token: "your-token-here"
    signing:
      keys:
        - key: |
            -----BEGIN PRIVATE KEY-----
            MHcCAQEEIL...
            -----END PRIVATE KEY-----

Choose the approach that works best for your organization:

  • Individual keys: Easier to manage few properties, better for tools that generate ConfigMaps/Secrets

  • application.yml: Better for many properties, easier to read and maintain

Both approaches can be mixed - some properties in individual keys, others in application.yml files.

Each section below details the configuration for these components.

2.3.3. Attestation Storage

Develocity Provenance Governor requires at least one attestation storage backend to publish attestations. You can configure:

  • Amazon S3 - Store attestations in S3 buckets

  • Artifactory - Store attestations using Artifactory’s evidence store

You may configure both storage backends to publish attestations to multiple locations simultaneously.

2.3.4. Develocity

To configure access to a Develocity instance, configure the following application properties:

develocity.instances.<instance-name>.uri=https://develocity.example.com (1)
1 <instance-name> is an identifier you choose (e.g., 'prod-dv' or 'main'). This name is used in access control policies and log messages. Use alphanumeric characters, dashes, or underscores.

Provide a Develocity access key in the sensitive application properties:

develocity.instances.<instance-name>.access-key=************ (your access key here)

2.3.5. Artifactory (Optional)

To configure access to an Artifactory instance for attestation storage, configure the following application properties:

artifactory.instances.<instance-name>.uri=https://artifactory.example.com (1)
1 <instance-name> is a name you choose to identify this Artifactory instance.

Artifactory configuration is optional. If you’re using S3 for attestation storage, you don’t need to configure Artifactory unless you want to publish attestations to both storage backends.

Provide an Artifactory access or ID token in the sensitive application properties (Secret secrets):

artifactory.instances.<instance-name>.access-token=************ (your access token here)
Advanced Artifactory Configuration

The following advanced properties can be configured for each Artifactory instance:

artifactory.instances.<instance-name>.path=/artifactory (1)
artifactory.instances.<instance-name>.graphql-path=/onemodel/api/v1/graphql (2)
artifactory.instances.<instance-name>.evidence-path=/evidence/api/v1/subject (3)
artifactory.instances.<instance-name>.retries-writing.attempts=3 (4)
artifactory.instances.<instance-name>.retries-writing.min-backoff=1s (5)
artifactory.instances.<instance-name>.retries-reading.attempts=3 (6)
artifactory.instances.<instance-name>.retries-reading.min-backoff=1s (7)
1 The base path to the Artifactory instance. Defaults to /artifactory.
2 The path to the Artifactory GraphQL API. Defaults to /onemodel/api/v1/graphql.
3 The path to the Artifactory Evidence Management API. Defaults to /evidence/api/v1/subject.
4 The maximum number of retry attempts for write operations to Artifactory. Defaults to 3.
5 The minimum backoff duration between retry attempts for write operations. Defaults to 1s.
6 The maximum number of retry attempts for read operations from Artifactory. Defaults to 3.
7 The minimum backoff duration between retry attempts for read operations. Defaults to 1s.

2.3.6. S3

To configure access to an Amazon S3 bucket for storing or reading attestations, configure the following application properties:

s3.instances.<instance-name>.region=us-east-1 (1)
s3.instances.<instance-name>.bucket-name=dpg-bucket-01 (2)
1 The AWS region where the bucket is located.
2 The name of the S3 bucket.
S3 Authentication

You can configure authentication using either static credentials, IAM roles, or environmental credentials. The application attempts to authenticate in the following order:

  1. IAM Role Assumption: Used if role-arn is configured.

  2. Static Credentials: Used if access-key-id and secret-access-key are configured.

  3. Container Credentials: (Default) Uses the environment’s credentials provider chain (e.g., IRSA in EKS, EC2 instance profile).

Static Credentials:

s3.instances.<instance-name>.access-key-id=AKIA...
s3.instances.<instance-name>.secret-access-key=...

IAM Role Assumption:

s3.instances.<instance-name>.role-arn=arn:aws:iam::123456789012:role/MyRole
s3.instances.<instance-name>.role-session-name=provenance-governor
AWS IAM Permissions

The IAM Role used by the application must have the following permissions on the target S3 bucket:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::<bucket-name>",
                "arn:aws:s3:::<bucket-name>/*"
            ]
        }
    ]
}
  • s3:PutObject: Required for publishing attestations.

  • s3:GetObject: Required for reading attestations (Fetch by ID, Policy Evaluation).

  • s3:ListBucket: Required for discovering attestations during Policy Evaluation.

Associate Service Account Role (EKS)

When running on Amazon EKS, you can associate an IAM role with a Kubernetes Service Account using IRSA (IAM Roles for Service Accounts).

To enable this, the Service Account used by the Develocity Provenance Governor API pod must be annotated with the ARN of the IAM role. The default deployment uses a Service Account named api in the develocity-provenance-governor namespace.

You can patch the Service Account after applying the manifest:

kubectl annotate serviceaccount api \
  -n develocity-provenance-governor \
  eks.amazonaws.com/role-arn=arn:aws:iam::123456789012:role/MyAttestationWriterRole

Alternatively, if you are using Kustomize to manage your deployment, you can create a kustomization.yaml to patch the manifest:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - manifest.yaml
patches:
  - target:
      kind: ServiceAccount
      name: api
      namespace: develocity-provenance-governor
    patch: |-
      - op: add
        path: /metadata/annotations
        value:
          eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/MyAttestationWriterRole
S3 Connection Tuning

The following properties allow tuning of the S3 connection and request handling:

s3.instances.<instance-name>.endpoint=https://s3.us-west-2.amazonaws.com (1)
s3.instances.<instance-name>.path-style-access=false (2)
s3.instances.<instance-name>.max-concurrency=50 (3)
s3.instances.<instance-name>.max-object-size=10MB (4)
1 The S3 endpoint URI. If omitted, the default AWS endpoint for the configured region is used.
2 Whether to use path-style access (e.g., https://s3.amazonaws.com/bucket/key). Defaults to false (virtual-hosted-style).
3 The maximum number of concurrent HTTP requests to S3. Defaults to the AWS SDK default.
4 The maximum allowed size for an attestation object. Defaults to 10MB.
S3 Timeouts
s3.instances.<instance-name>.connection-timeout=2s (1)
s3.instances.<instance-name>.read-timeout=30s (2)
s3.instances.<instance-name>.write-timeout=30s (3)
1 The timeout for establishing a connection to S3. Defaults to 2s.
2 The timeout for reading data from S3. Defaults to 30s.
3 The timeout for writing data to S3. Defaults to 30s.
S3 Retries
s3.instances.<instance-name>.retries.attempts=3 (1)
s3.instances.<instance-name>.retries.min-backoff=1s (2)
1 The maximum number of retry attempts for S3 operations. Defaults to 3.
2 The minimum backoff duration between retry attempts. Defaults to 1s.

Attestations are cryptographically signed using Dead Simple Signing Envelope (DSSE). Signing keys ensure attestation integrity and authenticity.

While signing keys are optional, they are strongly recommended for production deployments. Without signing keys:

  • Attestations are published but not signed

  • Recipients cannot verify attestation authenticity

  • Attestations cannot be trusted in policy evaluation

Important: Configuring signing keys allows Develocity Provenance Governor to sign attestations. To validate those signatures during policy evaluation, you must also create a TrustedPublicKeys policy containing the public key. See the verification section below for details.

Key Configuration in Kubernetes

Signing keys are configured in the secrets Secret using a specific naming pattern:

  • Private key: signing.keys[0].key - The private key in PEM format (stored in Secret secrets)

  • Friendly name: Derived from timestamp or identifier you choose (e.g., 20250818123202)

The application automatically loads private keys from the secrets Secret and extracts the corresponding public keys.

Generating Signing Keys

Develocity Provenance Governor supports three signing algorithms:

  • Ed25519 (Recommended) - 128-bit security, small key sizes, fast

  • ECDSA with prime256v1 - Elliptic curve, widely supported

  • RSA 2048 - Traditional, larger keys and signatures

Ed25519 provides the best balance of security, performance, and key size.

Generate Ed25519 private key
openssl genpkey -algorithm Ed25519 -out signing-key-20250818123202.private.pem
Extract public key
openssl pkey -in signing-key-20250818123202.private.pem -pubout -out signing-key-20250818123202.public.pem

Expected log message when application starts:

Signature support enabled, for key pair [name:20250818123202:keyid:b72af1:signing-algorithm:ed25519]
ECDSA Keys (Elliptic Curve)

ECDSA with the prime256v1 curve is supported by Artifactory and provides good security.

Generate EC private key and convert to PKCS#8 format
openssl ecparam -name prime256v1 -genkey -noout -out /tmp/ec_private.key
openssl pkcs8 -topk8 -inform PEM -in /tmp/ec_private.key -outform PEM -nocrypt -out signing-key-20250818123202.private.pem
Extract public key
openssl ec -in /tmp/ec_private.key -pubout -out signing-key-20250818123202.public.pem

Expected log message:

Signature support enabled, for key pair [name:20250818123202:keyid:a4f6cf:signing-algorithm:SHA256withECDSA]
RSA Keys

RSA 2048-bit keys are supported but produce larger signatures than Ed25519 or ECDSA.

Generate RSA private key
openssl genrsa -out signing-key-20250818123202.private.pem 2048
Extract public key
openssl rsa -in signing-key-20250818123202.private.pem -pubout -out signing-key-20250818123202.public.pem

Expected log message:

Signature support enabled, for key pair [name:20250818123202:keyid:4a1bcb:signing-algorithm:SHA256withRSA]
Adding Keys to Kubernetes Secret

After generating keys, add the private key to the secrets Secret.

Option 1: Using stringData with application.yml
apiVersion: v1
kind: Secret
metadata:
  name: secrets
  namespace: develocity-provenance-governor
type: Opaque
stringData:
  application.yml: |
    signing:
      keys:
        - key: |
            -----BEGIN PRIVATE KEY-----
            MHcCAQEEIL...
            -----END PRIVATE KEY-----
Option 2: Using individual property with base64
# Base64 encode the private key
base64 -i signing-key-20250818123202.private.pem | pbcopy

# Edit the secret
kubectl edit secret secrets --namespace develocity-provenance-governor

Add to the data: section:

data:
  signing.keys[0].key: <PASTE_BASE64_PRIVATE_KEY>

Key Naming Convention: Use a timestamp or version identifier in your key filenames for easy rotation:

  • signing-key-20250818123202.private.pem (timestamp format: YYYYMMDDHHmmss)

  • signing-key-v1.private.pem (version format)

This helps track which keys are in use and when to rotate them.

Verifying Signing Keys

After adding keys and restarting the deployment, check the logs:

kubectl rollout restart deployment/api --namespace develocity-provenance-governor
kubectl logs deployment/api --namespace develocity-provenance-governor | grep -i "Signature support"

You should see:

Signature support enabled, for key pair [name:20250818123202:keyid:b72af1:signing-algorithm:ed25519]

The keyid is a 6-character abbreviation of the SHA-256 digest of the public key, used to identify which key signed an attestation.

Configuring Public Keys for Attestation Validation

Simply generating and configuring signing keys allows Develocity Provenance Governor to sign attestations, but does not automatically enable validation of those signatures during policy evaluation.

To validate attestation signatures (including attestations published by this same instance), you must create a TrustedPublicKeys policy that includes the public key.

Without a TrustedPublicKeys policy:

  • Attestations are published and signed successfully

  • Policy scans can evaluate attestation content

  • Signature validation is not performed - signatures are ignored during policy evaluation

To enable signature validation:

  1. Extract the public key PEM content from your generated key pair

  2. Create a TrustedPublicKeys policy (see TrustedPublicKeys Policy section)

  3. Add the public key to the policy’s keys map with a descriptive name

  4. Deploy the policy to the policies ConfigMap

Example TrustedPublicKeys Policy
apiVersion: policy.gradle.com/v1
kind: TrustedPublicKeys
metadata:
  name: provenance-governor-signing-keys
  labels:
    policy.my-corp.com/gate: build
spec:
  description: Trust attestations signed by our Provenance Governor instance
  remediation: Ensure attestations are signed with approved keys
  keys:
    signing-key-20250818123202:
      pem: |-
        -----BEGIN PUBLIC KEY-----
        MCowBQYDK2VwAyEA7jKl3qN8mP4xVf2bR8cZ9hT6vL2sQ1nE5wJ8mK3pX7g=
        -----END PUBLIC KEY-----

This applies even when evaluating attestations published by the same Provenance Governor instance. The system treats all attestations equally and only validates signatures against explicitly trusted public keys defined in TrustedPublicKeys policies.

Uploading Public Keys to Artifactory (Optional)

If using Artifactory for attestation storage, upload the public key to establish trust:

  1. Navigate to your Artifactory instance

  2. Go to AdministrationSecurityKey ManagementPublic Keys

  3. Click Add Keys

  4. Provide an alias (e.g., provenance-governor-signing-key-20250818)

  5. Paste the contents of signing-key-20250818123202.public.pem or use:

    pbcopy < signing-key-20250818123202.public.pem
  6. Click Add Public Key

The alias in Artifactory is for human identification only and doesn’t affect functionality. The actual key matching uses the cryptographic key ID.

2.3.8. Access Control

Access control in Develocity Provenance Governor is configured using Access Control policies, which are YAML documents formatted like the other Develocity Provenance Governor policies. They assign access rights to identities and resources that the application has been configured with. Develocity Provenance Governor uses a single YAML file for access control policies, but it can include multiple policies by using YAML’s --- document separator.

apiVersion: policy.gradle.com/v1
kind: AccessControl
metadata:
  name: admin
spec:
  identityMatchingStrategy: (1)
    withBasicIdentity:
      - withName: "admin" (2)

    withOidc:
      - withIssuerUri: https://my-oidc-issuer.example.com (3)
        withClaims: (4)
          organization: "my-org"

  canPerform:
    - publish-attestations (5)
    - publish-policy-scans (6)
    - read-attestations (7)

  withResources: (8)
    - pkg:maven/* (9)
    - dv:my-develocity (10)
    - af:my-artifactory/* (11)
    - s3:my-s3-store/* (12)
1 Matchers for identities to apply this policy to. May match OIDC (OpenID Connect) identities using HTTP Bearer auth with OIDC access tokens, or basic identities using HTTP Basic auth.
2 Matches a basic identity with the username admin.
3 Matches an OIDC identity issued by the specified issuer.
4 Restricts the OIDC matches to tokens with the specified claims.
5 Grants the ability to publish attestations.
6 Grants the ability to perform policy evaluation.
7 Grants the ability to read attestations (e.g., via Fetch Attestation by ID API).
8 Resource matchers.
9 Resource matcher that matches all maven packages.
10 Resource matcher that matches a Develocity instance named my-develocity.
11 Resource matcher that matches all repositories in the Artifactory instance named my-artifactory.
12 Resource matcher that matches all resources in the S3 instance named my-s3-store.

While Develocity Provenance Governor uses a single YAML file for access control policies, you can include multiple policies in that file using YAML’s --- document separator.

Resources and matches

Access to resources is controlled by using resource matchers in access control policies. Matchers may match one or more resources. They are string matchers that support wildcards. The resources used are:

Resource Type Resource specifier

Packages

The pURL of the package. Supports wildcards (e.g., pkg:maven/org.example/*) to match multiple packages. See the PackageUrl Policy section for pattern syntax details.

Artifactory instance

af:<instance-name>/<repository>

Develocity instance

dv:<instance-name>/*

S3 instance

s3:<instance-name>/*

<instance-name> matches the name you configured for the instance (e.g. my-artifactory in artifactory.instances.my-artifactory.uri). The portion after the instance name represents the resource path relative to that instance. For Artifactory, you can restrict access to specific repositories. For Develocity and S3, only the wildcard /* is currently supported for the resource path.

Basic identities

Basic identities may be configured by specifying the following sensitive application property:

basic.identities.<username>=<password>

<password> uses Spring Security’s password encoder format, so you can use {noop}password for plaintext passwords, or use a password encoder such as bcrypt.

Actuator Basic Identities

To secure the Spring Boot Actuator endpoints, you can configure separate basic identities:

actuator.basic.identities.<username>=<password>

<password> uses Spring Boot’s password format, so you can use {noop}password for plaintext passwords, or use a password encoder such as bcrypt.

Password Values

The password value uses Spring Security’s format with a prefix indicating the encoding:

  • {bcrypt}$2a$10$…​ - Bcrypt-hashed (recommended for production)

  • {noop}mypassword - Plain text (only for testing, not recommended)

To generate a bcrypt password, use:

htpasswd -bnBC 10 "" password | tr -d ':\n'

2.3.9. Policies

Develocity Provenance Governor can be configured with policies to evaluate against packages. See Writing Policies for details on writing policies. Develocity Provenance Governor uses a single YAML file for policies, but it can include multiple policies by using YAML’s --- document separator.

2.3.10. Signing Keys

The signing key pair must be provided through a Kubernetes secret and mounted into the api container.

You should have already generated your signing key pair in the Prerequisites section. This section shows how to configure Develocity Provenance Governor to use those keys.

Step 1: Organize Your Keys

Create a directory structure for your keys with descriptive names:

mkdir -p keys/
# Copy your generated keys with a timestamp or version identifier
cp key-pair.private-pem keys/signing.key.20250818123202.private-pem
cp key-pair.public-pem keys/signing.key.20250818123202.public-pem

Your directory should look like:

keys/
  signing.key.20250818123202.private-pem
  signing.key.20250818123202.public-pem

Include a timestamp or version in key filenames to support key rotation. The pattern signing.key.YYYYMMDDHHMMSS.private-pem makes it easy to track which keys are in use.

Step 2: Create the Kubernetes Secret

Create a secret from your keys directory:

kubectl create secret generic keys \
  --from-file="keys/" \
  --namespace develocity-provenance-governor \
  --dry-run=client -o yaml > keys-secret.yaml

# Review the generated secret
cat keys-secret.yaml

# Apply the secret
kubectl apply -f keys-secret.yaml
Step 3: Configure Key References

The keys are automatically mounted at /workspace/config/secrets/keys/ in the container. Develocity Provenance Governor will discover and use keys matching the pattern signing.key.<timestamp>.private-pem and signing.key.<timestamp>.public-pem.

Step 4: Verify Keys Are Loaded

After deploying or restarting, check that keys were loaded successfully:

kubectl logs deployment/api -n develocity-provenance-governor | grep -i "signing key"

You should see log messages indicating that signing keys were loaded.

3. Using Develocity Provenance Governor

Before using Develocity Provenance Governor, verify your installation is working:

  1. Check that all pods are running:

    kubectl get pods -n develocity-provenance-governor
  2. Verify integrations are loaded by checking logs for 'support enabled' messages:

    kubectl logs deployment/api -n develocity-provenance-governor | grep "support enabled"
  3. Confirm authentication is working with a test request (expect 401 Unauthorized if not authenticated, or 200 if authenticated):

    curl -i http://your-provenance-governor-hostname

Once verified, you can start publishing attestations and evaluating policies on software packages.

It is recommended to first start by publishing attestations for software packages. This creates the data foundation that policies will evaluate against. Once you are confident that attestations are being published correctly, you can start writing policies and evaluating them against software packages. This approach allows you to verify attestation generation is working before adding policy enforcement, reducing troubleshooting complexity.

3.1. Publishing Attestations

You can publish attestations for a software package by calling the Develocity Provenance Governor API directly, or by using the Develocity Provenance Governor GitHub Action.

In both cases, the following information will be needed:

Information Where to Find It Example

Package type

Your build system or registry type

oci, maven

Package name

Artifact name from your build

my-app

Package version

Build version or tag

1.0.0

SHA-256 digest

Build output or registry metadata

sha256:73f482a2…​

Repository URL

Artifactory repository where package is stored

artifactory.example.com/maven-repo-local

Build Scan IDs or Query

Develocity Build Scan URLs or advanced search

bjvognekiphus or search query

3.1.1. When to Publish Attestations

You can publish attestations for a software package at any time after the package has been built and published to Artifactory.

This flexibility allows you to optimize your workflow:

  • Immediate publishing: Publish right after the build for complete tracking of all artifacts

  • Gated publishing: Only publish for packages that pass quality gates to reduce storage costs

  • Batch publishing: Collect multiple builds and publish attestations together for efficiency

Delaying attestations until it is known the software package will move forward in the software supply chain can help reduce the number of attestations that need to be stored and managed. For example, you may choose to only publish attestations for packages that have passed an initial quality gate.

For details on how to publish attestations using the Develocity Provenance Governor API, see _Appendix A: API Reference.

For details on how to publish attestations using the Develocity Provenance Governor GitHub Action, see GitHub Actions.

3.1.2. Attestation Types

Develocity Provenance Governor generates attestations which are compliant with the in-toto Attestations Framework. Each attestation is a JSON document wrapped in a signing envelope and has a subject and a predicate. The predicate has a type that describes the kind of attestation it is, as well as properties specific to that predicate type.

A more detailed description of the attestations published by Develocity Provenance Governor can be found in Appendix: Attestation Reference.

The table below lists the types of attestations Develocity Provenance Governor can generate:

Attestation Predicate Type Description

Build Tool

https://gradle.com/attestation/build-tool/v1

Attests to the build tool used to create the package, including its version.

Java Toolchain

https://gradle.com/attestation/java-toolchains/v1

Attests to the Java toolchain used to build the package, including the JDK version and vendor.

Resolved Dependencies Repositories

https://gradle.com/attestation/resolved-dependencies-repositories/v1

Attests to which repository resolved dependencies were fetched from when building the package.

Resolved Dependencies

https://gradle.com/attestation/resolved-dependencies/v1

Attests to the dependencies that were resolved when building the package.

3.2. Writing Policies

Develocity Provenance Governor can evaluate policies against software packages to determine if they are compliant with your organization’s requirements. Develocity Provenance Governor uses the published attestations to evaluate policies.

Policies evaluate against the attestations generated by Develocity Provenance Governor. If you need to enforce requirements not covered by the current attestation types, contact support to discuss your use case.

Develocity Provenance Governor uses declarative YAML policies instead of code-based policy languages (like Rego in Open Policy Agent). This makes policies easier to read, write, and version control alongside your deployment configuration.

3.2.1. Policy Architecture Overview

Before writing policies, it’s important to understand how the different components work together:

  1. Policies define specific rules (e.g., "must use Java 21", "cannot use Oracle JDK")

  2. Labels categorize and tag policies (e.g., policy.my-corp.com/gate: build)

  3. PolicyScanDefinitions select which policies to evaluate using label selectors

  4. API Calls execute a PolicyScanDefinition against a specific package

  5. Results show which policies passed or failed for that package

Example Flow:

# 1. Write a policy with a label
apiVersion: policy.gradle.com/v1
kind: JavaToolchains
metadata:
  name: require-java-21
  labels:
    policy.my-corp.com/gate: production  # <-- Label
spec:
  matchingStrategy: must-match
  toolchains:
    - vendor: Eclipse Adoptium
      versions: ["21.0.5"]

# 2. Create a PolicyScanDefinition that selects policies by label
---
apiVersion: policy.gradle.com/v1
kind: PolicyScanDefinition
metadata:
  name: production-gate
spec:
  description: Policies for production deployments
  policy-selector:
    match-labels:
      policy.my-corp.com/gate: production  # <-- Selects all policies with this label

# 3. Execute the scan via API (references the PolicyScanDefinition name)
# POST /packages/maven/com.example/my-app/1.0.0/policy-scans/production-gate

This architecture allows you to:

  • Write policies once and reuse them across multiple scans

  • Create different policy sets for different environments (dev, staging, production)

  • Organize policies by team, compliance requirement, or deployment stage

  • Change which policies are enforced without modifying individual policies

3.2.2. Types of policies

The table below lists the kinds of policies you can define in Develocity Provenance Governor:

Policy Kind Description

BuildTool

Checks for the use of a specific build tool and version. Can be used to deny use of specific build tools or versions, or require the use of a specific build tool or version.

JavaToolchains

Checks for the use of a specific Java toolchain (JDK version and vendor). Can be used to deny use of specific JDK versions or vendors, or require the use of a specific JDK version or vendor.

ResolvedDependenciesRepositories

Checks for the use of specific repositories to resolve dependencies. Can be used to prevent the use of specific repositories, or require the use of a specific repository.

PackageUrl

Checks for the use of specific dependencies. Can be used to prevent the use of specific dependencies, or require the use of a specific version of a dependency.

AttestationsExist

Checks for the presence of specific types of attestations for a package. Can be used to ensure that required metadata (e.g., build tool info) has been captured.

TrustedPublicKeys

Checks the signatures on attestations to verify they were signed with a trusted key and that the attestation has not been tampered with.

PolicyScanDefinition

Groups multiple policies together for execution using label selectors. Used to create reusable policy scan configurations (e.g., "build-gate", "production-gate") that can be triggered via the API.

The following sections describe each policy type in more detail, along with examples of how to write them.

3.2.3. General policy structure

All policies share a common structure. Policies are modeled as Kubernetes-style custom resources, with apiVersion, kind, metadata, and spec properties.

Policies are not true Kubernetes resources, but the format makes them recognizable to those familiar with Kubernetes.

The following snippet demonstrates the general structure used by all policies:

Example: General Policy Structure
apiVersion: policy.gradle.com/v1
kind: TypeOfPolicy (1)
metadata: (2)
  name: friendly-name (3)
  labels: (4)
    policy.my-corp.com/gate: build
    policy.my-corp.com/owner: DevSecOps
spec:
  description: Human-readable description of the policy. (5)
  remediation: Human-readable remediation steps if the policy is violated. (6)
  matchingStrategy: must-match or none-match (7)
  resultsLabels: (8)
    policy.my-corp.com/cvss: <critical|high|medium|low> # common for vulnerability management
    policy.my-corp.com/severity: <minor|sever|warning> # common for compliance and regulatory enforcement / risk assessment
    policy.my-corp.com/dry-run: true # domain specific
    policy.my-corp.com/severity-level: <SEV-1,SEV-2,SEV-3> # common for incident management
1 The kind property defines the type of policy. This will drive what properties are required in the spec property.
2 The metadata.* properties are similar to traditional Kubernetes metadata information which includes name, labels, annotations.
3 The name property is a unique identifier for the policy.
4 Policies that set metadata.labels can be selected by policy scans for inclusion if the label satisfies the selector defined. A policy can be included in multiple policy scans. See Evaluating Policies for more information.
5 The spec.description explains in human-readable terms what the policy is attesting to about a software package.
6 The spec.remediation explains in human-readable terms what will be reported back in the policy scan and what needs done to the software package to make the package compliant.
7 The spec.matchingStrategy defines how the policy values are evaluated against the attestation data:
  • must-match - must match one of the values on the policy.

  • none-match - must not match any of the values on the policy.

8 The spec.resultsLabels allows the policy author to define key value pairs to write to the policy results. See Result labels for more information.

Each kind of policy defines policy-specific properties (under the spec object). These properties are covered in the individual sections for each policy.

You can include multiple policies in a single YAML file by separating them with ---.

Result labels

Result labels are useful when policies are evaluated as part of a policy scan.

The labels can be used to categorize and filter policy scan results based on severity, compliance level, or other criteria relevant to your organization’s governance processes. For example, you might use result labels to indicate the severity of a policy violation (e.g., critical, high, medium, low), the type of compliance issue (e.g., security, license, quality), or the team responsible for addressing the issue (e.g., frontend, backend, devops).

3.2.4. BuildTool

A BuildTool policy enforces which build tools are allowed or disallowed in builds. Specific tools and versions can be required or prohibited.

This policy operates on the https://gradle.com/attestation/build-tool/v1 predicate type.

Example: Require Specific Build Tools and Versions
kind: BuildTool
apiVersion: policy.gradle.com/v1
metadata:
  name: example-build-tool-policy
  labels:
    policy.my-corp.com/gate: build
  annotations:
    createdBy: admin
spec:
  resultsLabels:
    policy.my-corp.com/gate: build
  description: Require specific build tools and versions for CI pipelines
  remediation: Use an approved build tool and version
  matchingStrategy: must-match
  buildTools:
    - toolType: gradle
      toolVersions:
        - "7.6"
        - "8.0"
      agentVersions:
        - "1.0"
        - "2.0"
    - toolType: maven
      toolVersions:
        - "3.8.6"
      agentVersions:
        - "1.0"

3.2.5. JavaToolchains

A JavaToolchains policy enforces which Java toolchains are allowed or disallowed in builds. Specific vendors and versions can be required or prohibited.

Scenario: Your organization has banned Oracle JDK due to licensing changes and requires all teams to use BellSoft Liberica JDK version 21.0.5 or later.

Solution: Create two policies - one to deny Oracle, one to require BellSoft:

Example: Disallow Oracle Java Toolchains
apiVersion: policy.gradle.com/v1
kind: JavaToolchains
metadata:
  name: none-match-oracle-java-toolchain
  labels:
    policy.my-corp.com/gate: build
spec:
  resultsLabels:
    policy.my-corp.com/gate: build
  description: Disallow Oracle Java Toolchains in builds.
  remediation: Update the Gradle build to use BellSoft Java toolchain.
  matchingStrategy: none-match
  toolchains:
  - vendor: oracle
Example: Require BellSoft Java Toolchains
apiVersion: policy.gradle.com/v1
kind: JavaToolchains
metadata:
  name: must-match-bellsoft-java-toolchain
  labels:
    policy.my-corp.com/gate: build
spec:
  resultsLabels:
    policy.my-corp.com/gate: build
  description: Builds must use BellSoft Java toolchains in builds.
  remediation: Update the build to use BellSoft Java toolchain.
  matchingStrategy: must-match
  toolchains:
  - vendor: BellSoft Liberica
    versions:
    - 21.0.8
    - 21.0.7
    - 21.0.6
    - 21.0.5

3.2.6. ResolvedDependenciesRepositories

A ResolvedDependenciesRepositories policy controls the dependency repositories which are allowed to be used to resolve and fetch dependencies.

This policy operates on the https://gradle.com/attestation/resolved-dependencies-repositories/v1 predicate type.

Example: Allow Only Approved Repositories
apiVersion: policy.gradle.com/v1
kind: ResolvedDependenciesRepositories
metadata:
  name: must-match-repositories
  labels:
    policy.my-corp.com/gate: build
spec:
  resultsLabels:
    policy.my-corp.com/gate: build
  description: Allow resolved dependencies repositories
  remediation: Remove the offending repository from build configuration
  matchingStrategy: must-match
  uris:
  - https://artifacts.example.com/maven2
  - https://repo.example.com/artifactory/libs-release-candidates-local/
  - https://plugins.gradle.org/m2/
  - https://repo.example.com/artifactory/public

3.2.7. PackageUrl

A PackageUrl policy controls which dependencies are allowed or disallowed in builds. This can be used to block or require specific libraries.

This policy operates on the https://gradle.com/attestation/resolved-dependencies/v1 predicate type.

Dependencies are identified using Package URLs (pURLs), which provide a standardized way to reference software packages across different ecosystems.

Package URL Pattern Syntax

Package URLs in policies support wildcard matching using the asterisk (*) character:

Pattern Matches Example

pkg:maven/org.example/artifact

Exact package, any version (e.g., pkg:maven/org.example/artifact@1.0.0)

pkg:maven/org.example/artifact@1.0.0

pkg:maven/org.example/artifact@1.0.0

Exact package and version

Only pkg:maven/org.example/artifact@1.0.0

pkg:maven/org.example/artifact@1.0.*

Exact package, wildcard patch version

pkg:maven/org.example/artifact@1.0.0, pkg:maven/org.example/artifact@1.0.5

pkg:maven/org.example/*

All artifacts in namespace

pkg:maven/org.example/foo, pkg:maven/org.example/bar

pkg:maven/org.example/*@1.0.0

All artifacts in namespace with specific version

pkg:maven/org.example/foo@1.0.0, pkg:maven/org.example/bar@1.0.0

pkg:maven/*/*

All Maven artifacts

Any Maven package

pkg:npm/@scope/*

All packages in npm scope

pkg:npm/@scope/package-a, pkg:npm/@scope/package-b

Wildcards ( * ) can be used in the namespace, artifact name, and at the end of version components (e.g., 1.0.*). All matching (type, namespace, name, version) is case-insensitive.

Example: Disallow Lombok Dependency
apiVersion: policy.gradle.com/v1
kind: PackageUrl
metadata:
  name: none-match-purl-lombok
  labels:
    policy.my-corp.com/gate: build
spec:
  resultsLabels:
    policy.my-corp.com/gate: build
  description: Disallow lombok dependency
  remediation: Remove lombok dependency from project
  matchingStrategy: none-match
  purls:
  - pkg:maven/org.projectlombok/lombok
Example: Find Usages of Spring 6.2.7
apiVersion: policy.gradle.com/v1
kind: PackageUrl
metadata:
  name: none-match-purl-spring-beans-6.2.7
  labels:
    policy.my-corp.com/gate: build
spec:
  resultsLabels:
    policy.my-corp.com/gate: build
  description: Find usages of spring 6.2.7
  remediation: Upgrade dependency to spring 6.2.7
  matchingStrategy: none-match
  purls:
  - pkg:maven/org.springframework/*@6.2.7
Example: Require Approved Patch Versions
apiVersion: policy.gradle.com/v1
kind: PackageUrl
metadata:
  name: must-match-approved-spring-versions
  labels:
    policy.my-corp.com/gate: build
spec:
  resultsLabels:
    policy.my-corp.com/gate: build
  description: Ensure only approved patch versions of Spring Framework are used.
  remediation: Upgrade or downgrade to an approved patch version (e.g., 6.0.10, 6.1.5).
  matchingStrategy: must-match
  purls:
  - pkg:maven/org.springframework/spring-core@6.0.10
  - pkg:maven/org.springframework/spring-beans@6.0.10
  - pkg:maven/org.springframework/spring-web@6.0.10
  - pkg:maven/org.springframework/spring-core@6.1.5
  - pkg:maven/org.springframework/spring-beans@6.1.5
  - pkg:maven/org.springframework/spring-web@6.1.5

3.2.8. AttestationsExist

An AttestationsExist policy operates on the predicateType field of all attestations associated with a software package. This can be used to ensure that critical metadata, regardless of its origin, has been captured and published.

Develocity Provenance Governor currently generates attestations with the following predicate types:

Example: Require Build Tool Attestation
apiVersion: policy.gradle.com/v1
kind: AttestationsExist
metadata:
  name: require-build-tool-attestation
  labels:
    policy.my-corp.com/gate: build
spec:
  resultsLabels:
    policy.my-corp.com/gate: build
  description: Require Build Tool attestation to be present.
  remediation: Ensure the build publishes a Build Tool attestation.
  expectedPredicates:
  - https://gradle.com/attestation/build-tool/v1
Example: Require Verification Summary Attestation
apiVersion: policy.gradle.com/v1
kind: AttestationsExist
metadata:
  name: require-verification-summary-attestation
  labels:
    policy.my-corp.com/gate: production
spec:
  resultsLabels:
    policy.my-corp.com/gate: production
  description: Require Verification Summary attestation (VSA) to be present.
  remediation: Ensure the package has passed a policy scan which generates a VSA.
  expectedPredicates:
  - https://slsa.dev/verification_summary/v1

Verification Summary Attestations (VSAs) are automatically generated when a Policy Scan is executed. Requiring a VSA ensures that the package has been evaluated against a set of policies before deployment.

3.2.9. TrustedPublicKeys

A TrustedPublicKeys policy defines which public keys are trusted for verifying attestation signatures. This policy is used to ensure that only attestations signed with approved keys are accepted during policy evaluation.

This policy operates on the attestation envelope and verifies the signature against the payload.

Example: TrustedPublicKeys
apiVersion: policy.gradle.com/v1
kind: TrustedPublicKeys
metadata:
  name: trusted-keys
  labels:
    policy.my-corp.com/gate: build
spec:
  description: For the Java 21 toolchain, validate attestation signatures
  remediation: Make sure the attestations are signed with the expected key
  keys:
    my-signing-key: (1)
      pem: |- (2)
           -----BEGIN PUBLIC KEY-----
           MCowBQYDK2VwAyEAVzZ+Xr2RqM6RvYcOmxPADdMi7u8pJ7L8Fv6HnQz/xJg=
           -----END PUBLIC KEY-----
    my-b64-key: (3)
      pemBase64: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUNvd0JRWURLMlZ3QXlFQVZ6WitYcjJScU02UnZZY09teFBBRGRNaTd1OHBKN0w4RnY2SG5Rei94Smc9Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=
1 A unique identifier for this key. Use a descriptive name that indicates its purpose or rotation date.
2 The public key in PEM format. Use this format when embedding the key directly in your policy file.
3 The public key in Base64-encoded PEM format. Use this format when the key is stored as a single-line string.

The keys map allows you to specify one or more trusted public keys, either as PEM or Base64-encoded PEM. This policy is typically referenced by label in a PolicyScanDefinition to enforce signature validation on attestations.

3.3. Evaluating Policies

Develocity Provenance Governor performs "Policy Scans" to evaluate policies against a software package. Policy Scans are defined declaratively in YAML using a PolicyScanDefinition. Policy Scan definitions are stored alongside policies.

Policy Scans execute during your CI/CD pipeline, typically:

  1. After building and publishing a package

  2. After attestations are published to Artifactory

  3. Before promoting to the next environment

  4. Before deploying to production

The scan results determine whether the pipeline proceeds or fails based on policy compliance.

When a Policy Scan is executed, Develocity Provenance Governor retrieves the relevant policies based on the label selectors defined in the PolicyScanDefinition. It then evaluates each policy against the attestations associated with the specified software package.

Policy scans help to create reusable, targeted validation strategies by grouping related policies together.

Choose a policy scan organization strategy:

Strategy Use When Example Scans

By Environment

Different rules per deployment stage

dev-scan, staging-scan, prod-scan

By Package Type

Different rules per artifact type

oci-scan, maven-scan

By Team

Different teams have different rules

frontend-scan, backend-scan

By Compliance

Regulatory requirements vary

soc-scan, hipaa-scan, pci-scan

Most organizations combine strategies, for example: prod-backend-scan or staging-frontend-scan.

3.3.1. Verification Summary Attestation

When a Policy Scan is executed, Develocity Provenance Governor automatically generates and publishes a Verification Summary Attestation (VSA). This VSA serves as a durable, signed record that the specific artifact was verified against the specific policy at a specific time.

The VSA follows the SLSA Verification Summary v1.2 specification and provides cryptographically verifiable evidence of policy compliance.

How VSAs Are Generated

For each Policy Scan execution:

  1. Policies are evaluated against the artifact’s attestations

  2. Results are collected - all policy evaluation results (satisfied, unsatisfied, not-applicable)

  3. VSA is created with:

    • Verification result (PASSED if all applicable policies satisfied, FAILED otherwise)

    • Reference to the Policy Scan Definition used

    • List of input attestations that were evaluated

    • Timestamp of verification

    • Verifier information (Develocity Provenance Governor instance)

  4. VSA is signed using the configured signing keys

  5. VSA is published to all configured attestation stores

VSA Storage and Retrieval

Where VSAs Are Stored:

The VSA is published to all attestation store instances configured for your deployment (Artifactory, S3, or both). The VSA is stored alongside the artifact’s other attestations using the same storage hierarchy.

Retrieving a VSA:

VSAs can be retrieved using the standard "Fetch Attestation by ID" endpoint:

curl --request GET \
  --url https://provenance-governor.example.com/packages/maven/com.example/my-app/1.0.0/sha256:abc.../attestations/s3:prod/vsa-uuid \
  --header 'authorization: Basic ***********=' \
  --header 'accept: application/json'

The VSA identifier and storage location are logged during policy scan execution.

VSA Structure:

The returned VSA is a DSSE envelope containing an in-toto Statement with predicate type https://slsa.dev/verification_summary/v1. See the Verification Summary Predicate Reference for the complete structure.

VSA Signing Configuration

VSAs are signed using the same signing keys configured for other attestations. Ensure you have configured signing keys in your application configuration:

signing:
  keys:
    - key: |
        -----BEGIN EC PRIVATE KEY-----
        MHcCAQEEIL...
        -----END EC PRIVATE KEY-----

Without signing keys configured, VSAs will still be generated but will not be cryptographically signed.

Failure Handling

VSA Publication Failures:

If VSA publication to any attestation store fails:

  • The error is logged but does not fail the policy scan

  • Policy scan results are still returned to the caller

  • The policy scan continues to return success/failure based on policy evaluation, not VSA publication

This design ensures that policy enforcement is not disrupted by transient storage issues.

Troubleshooting VSA Publication:

If VSAs are not being published, check:

  1. Signing keys are configured - VSAs require signing keys for production use

  2. Write permissions - The service account must have write access to attestation stores

  3. Storage connectivity - Verify network access to S3/Artifactory instances

  4. Logs - Check application logs for VSA publication errors

Access Control for VSA Publication

Because the Policy Scan operation now involves writing a new attestation (the VSA) to the storage, the user or service account performing the scan must have write permissions to the target repository in the Attestation Store.

Ensure your AccessControl policies are configured to allow the authenticated identity to write to the repository where the artifact’s attestations are stored. If the user only has read access, the Policy Scan will fail to publish the VSA (but will still return policy evaluation results).

3.3.2. PolicyScanDefinition

A PolicyScanDefinition defines a PolicyScan and selects which policies to apply using label selectors. This allows grouping and targeting of policies for specific enforcement scenarios.

Example: PolicyScanDefinition
apiVersion: policy.gradle.com/v1
kind: PolicyScanDefinition
metadata:
  name: build-gate
  labels:
    policy.my-corp.com/gate: build
spec:
  description: Policy scan definition for the build gate
  policy-selector:
    match-labels:
      policy.my-corp.com/gate: build

The above policy scan will include all policies labeled with policy.my-corp.com/gate: build.

3.4. Example: Build Gate Scenario

This example demonstrates a "Build Gate" scenario, where a set of policies are applied to an artifact. The goal is to ensure that any artifact passing this gate meets specific criteria, such as being built with a trusted build tool and having the necessary attestations.

1. Label Policies

Ensure all relevant policies are labeled with policy.my-corp.com/gate: build. For example:

apiVersion: policy.gradle.com/v1
kind: BuildTool
metadata:
  name: require-gradle
  labels:
    policy.my-corp.com/gate: build
spec:
  # ... policy details ...
2. Define Policy Scan

Create a PolicyScanDefinition that selects these policies.

apiVersion: policy.gradle.com/v1
kind: PolicyScanDefinition
metadata:
  name: build-gate
spec:
  description: Validate build standards for the build gate
  policy-selector:
    match-labels:
      policy.my-corp.com/gate: build
3. Evaluate

Trigger the policy scan via the API to validate the package against the build gate requirements:

curl --request POST \
  --url https://provenance-governor.example.com/packages/maven/com.example/my-app/1.0.0/policy-scans/build-gate \
  --header 'authorization: Basic ...' \
  --header 'content-type: application/json' \
  --data '{ ... }'

3.5. Understanding Policy Scan Results

Policy scan results provide detailed information about which policies passed or failed for a package.

The format of the response depends on the Accept header provided in the request:

  • application/json: The server collects all results and returns them as a single JSON array. This is the default behavior.

  • application/x-ndjson: The server streams individual results as Newline Delimited JSON objects as soon as they are available. This is recommended for CI/CD pipelines to provide immediate feedback and prevent timeouts.

3.5.1. HTTP Status Codes

Status Code Meaning

200 OK

Policy scan completed. Check status field: SUCCESS means all policies passed, FAILURE means one or more policies failed.

400 Bad Request

Invalid request (missing required fields, invalid package URL format, etc.).

401 Unauthorized

Authentication credentials missing or invalid.

403 Forbidden

Authenticated but not authorized to scan this package.

404 Not Found

Policy scan definition not found, or package/attestations not found.

500 Internal Server Error

Server error during policy evaluation.

3.5.2. Interpreting Results

Successful Scan (All Policies Pass): When all policies pass, the API will return an array where each PolicyScan.Result object has a status of satisfied or not-applicable.

[
  {
    "labels": {
      "policy.my-corp.com/gate": "build"
    },
    "status": "satisfied",
    "policyUri": "/policies/BuildTool/only-gradle-builds",
    "policyDescription": "Ensure only Gradle is used as the build tool.",
    "policyRemediation": "Use Gradle as the build tool. Consult migration guides if necessary.",
    "attestationUri": "/packages/maven/com.example/my-app/1.0.0/sha256:73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497/attestations/jfrog-artifactory:prod/maven-app-1.0.0-build-env",
    "sourcedFromUri": "https://develocity.example.com/s/abcdef1234567",
    "details": {
      "buildTool": "Gradle",
      "predicateType": "https://gradle.com/attestation/build-tool/v1"
    }
  },
  {
    "labels": {
      "policy.my-corp.com/gate": "build"
    },
    "status": "satisfied",
    "policyUri": "/policies/TrustedPublicKeys/release-pipeline-signer",
    "policyDescription": "Verify all attestations are signed by the release pipeline's trusted key.",
    "policyRemediation": "Ensure the attestation is signed by a trusted key from the release pipeline. Re-run the build if necessary.",
    "attestationUri": "/packages/maven/com.example/my-app/1.0.0/sha256:73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497/attestations/jfrog-artifactory:prod/maven-app-1.0.0-release",
    "sourcedFromUri": null,
    "details": {
      "keyIds": ["abcd1234"],
      "predicateType": "https://in-toto.io/Statement/v0.1"
    }
  }
]

Failed Scan (One or More Policies Fail): When one or more policies fail, the API will return an array that includes PolicyScan.Result objects with a status of unsatisfied.

[
  {
    "labels": {
      "policy.my-corp.com/gate": "build"
    },
    "status": "unsatisfied",
    "policyUri": "/policies/ResolvedDependenciesRepositories/disallow-untrusted-repos",
    "policyDescription": "Disallow untrusted Maven repositories.",
    "policyRemediation": "Remove 'https://untrusted.repo.com/maven2' from your build's dependency repositories.",
    "attestationUri": "/packages/maven/com.example/my-app/1.0.0/sha256:73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497/attestations/jfrog-artifactory:prod/maven-app-1.0.0-build",
    "sourcedFromUri": "https://develocity.example.com/s/abcdef1234567",
    "details": {
      "uris": ["https://untrusted.repo.com/maven2"],
      "predicateType": "https://gradle.com/attestation/resolved-dependencies-repositories/v1"
    }
  },
  {
    "labels": {
      "policy.my-corp.com/gate": "build"
    },
    "status": "satisfied",
    "policyUri": "/policies/BuildTool/only-gradle-builds",
    "policyDescription": "Ensure only Gradle is used as the build tool.",
    "policyRemediation": "Use Gradle as the build tool. Consult migration guides if necessary.",
    "attestationUri": "/packages/maven/com.example/my-app/1.0.0/sha256:73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497/attestations/jfrog-artifactory:prod/maven-app-1.0.0-build-env",
    "sourcedFromUri": "https://develocity.example.com/s/abcdef1234567",
    "details": {
      "buildTool": "Gradle",
      "predicateType": "https://gradle.com/attestation/build-tool/v1"
    }
  }
]

3.5.3. Using Results in CI/CD

In a CI/CD pipeline, you can use application/x-ndjson to stream the API response directly into jq. This allows you to filter for any policies with an unsatisfied status as they are returned, without waiting for the entire scan to complete.

#!/bin/bash

# Stream results and filter for unsatisfied policies
# We use curl with 'Accept: application/x-ndjson' to fetch the results as a stream.
# jq filters the stream for any object where .status == "unsatisfied"

FAILED_POLICIES=$(curl -s --request POST \
  --url "https://provenance-governor.example.com/packages/maven/com.example/my-app/1.0.0/policy-scans/build-gate" \
  --header "authorization: Basic ${AUTH_TOKEN}" \
  --header "Accept: application/x-ndjson" \
  --header "content-type: application/x-www-form-urlencoded" \
  --data-urlencode "sha256=a30f98e704871a244ac3f28c2ada5c120afe756e981438f221e21fff3042a11b" \
  --data-urlencode "repositoryUrl=repo.example.com/docker-trial" \
  | jq -c 'select(.status == "unsatisfied")')

if [ -n "$FAILED_POLICIES" ]; then
  echo "Policy scan FAILED. The following policies were unsatisfied:"
  echo "$FAILED_POLICIES" | jq .
  exit 1
else
  echo "Policy scan PASSED."
fi

Use the labels in policy results to categorize failures by severity, team ownership, or compliance requirement for better reporting and alerting.

3.6. Integrating with Continuous Integration and Continuous Deployment systems

3.6.1. GitHub Actions

We provide GitHub Actions for easy integration with GitHub workflows. The gradle/develocity-provenance-governor-actions/publish calls the publishing API to publish attestations for a package, and the gradle/develocity-provenance-governor-actions/enforce enforces that a policy scan passes for a package.

Both actions require a GitHub token to authenticate with the Develocity Provenance Governor API. You can base your access control policy on this token. For example:

apiVersion: policy.gradle.com/v1
kind: AccessControl
metadata:
  name: example
spec:
  identityMatchingStrategy:
    withOidc:
    - withIssuerUri: "https://token.actions.githubusercontent.com"
      withClaims:
        repository_owner: example-org
Example Publishing
uses: gradle/develocity-provenance-governor-actions/publish@main
with:
  attestation-publisher-url: 'https://provenance-governor.example.com'
  build-scan-ids: eo5xxyg3drtoc
  build-scan-queries: 'value:"CI run=${{ github.run_id }}"'
  subject-type: oci
  subject-name: java-payment-calculator
  subject-version: 1.2.3
  subject-digest: 1a6b2bf83435f2a9ccd33519ad3e817bf79aee6af1c7a15d26d8a256bfa9cc94
  subject-repository-url: develocitytia.jfrog.io/docker-trial
Example Enforcement
uses: gradle/develocity-provenance-governor-actions/enforce@main
with:
  policy-evaluator-url: 'https://provenance-governor.example.com'
  subject-type: oci
  subject-name: java-payment-calculator
  subject-version: 1.2.3
  subject-digest: 1a6b2bf83435f2a9ccd33519ad3e817bf79aee6af1c7a15d26d8a256bfa9cc94
  subject-repository-url: develocitytia.jfrog.io/docker-example-repo
  policy-scan: ci-enforcement

3.6.2. Integration with Other CI/CD Systems

Develocity Provenance Governor exposes a REST API that can be used to publish attestations and evaluate policies. You can integrate with any CI/CD system that can make HTTP requests.

For example, you can use curl to publish attestations and evaluate policies from a shell script:

# Set up credentials
export DPG_URL="https://provenance-governor.example.com"
export DPG_AUTH=$(echo -n 'username:password' | base64)

# Publish attestations
curl -X POST "$DPG_URL/packages/maven/com.example/my-app/1.0.0/attestations" \
  -H "Authorization: Basic $DPG_AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "sha256": "73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497",
    "repositoryUrl": "artifactory.example.com/maven-repo-local",
    "buildScan": {
      "ids": ["bjvognekiphus"]
    }
  }'

See Appendix A: API Reference for complete details on the API endpoints and request/response formats, as well as example curl commands.

4. Troubleshooting

4.1. Operations

4.1.1. Updating Develocity Provenance Governor

To update to a new version:

  1. Review the release notes for breaking changes and new features.

  2. Update the container image in your deployment:

    # Edit the deployment to use the new image version
    kubectl set image deployment/api api-http=registry.gradle.com/develocity/provenance-governor:NEW_VERSION@sha256:NEW_DIGEST \
      -n develocity-provenance-governor
    
    # Watch the rollout
    kubectl rollout status deployment/api -n develocity-provenance-governor
  3. Verify the update:

    # Check pod is running with new image
    kubectl get pods -n develocity-provenance-governor -o jsonpath='{.items[0].spec.containers[0].image}'
    
    # Check logs for successful startup
    kubectl logs deployment/api -n develocity-provenance-governor --tail=50

Before updating production, test the new version in a non-production environment with your existing configurations.

4.1.2. Rolling Back to a Previous Version

If you encounter issues after an update:

  1. Rollback the deployment:

    kubectl rollout undo deployment/api -n develocity-provenance-governor
  2. Verify rollback succeeded:

    kubectl rollout status deployment/api -n develocity-provenance-governor
    kubectl logs deployment/api -n develocity-provenance-governor --tail=50
  3. Check rollout history:

    kubectl rollout history deployment/api -n develocity-provenance-governor

4.1.3. Updating Configuration Without Downtime

Configuration changes (ConfigMaps, Secrets) require a pod restart to take effect:

  1. Update the ConfigMap or Secret:

    # For policies
    kubectl create configmap policies \
      --from-file=policies/ \
      --dry-run=client -o yaml | kubectl apply -f -
    
    # For application properties
    kubectl create configmap properties \
      --from-file=application.properties \
      --dry-run=client -o yaml | kubectl apply -f -
    
    # For secrets
    kubectl create secret generic secrets \
      --from-file=secrets/ \
      --dry-run=client -o yaml | kubectl apply -f -
  2. Restart the deployment:

    kubectl rollout restart deployment/api -n develocity-provenance-governor
    kubectl rollout status deployment/api -n develocity-provenance-governor
  3. Verify configuration loaded:

    kubectl logs deployment/api -n develocity-provenance-governor | grep "support enabled"

4.1.4. Managing Policies

Adding a new policy:

  1. Add the policy YAML to your policies directory.

  2. Update the policies ConfigMap.

  3. Restart the deployment.

  4. Verify the policy is loaded in the logs.

Disabling a policy temporarily:

Remove the labels that PolicyScanDefinitions use to select it. The policy will remain in the ConfigMap but won’t be evaluated.

Testing policy changes:

  1. Create a new PolicyScanDefinition with a unique name for testing.

  2. Use labels to select only the new/modified policies.

  3. Test against known packages.

  4. Once verified, update your production PolicyScanDefinition.

4.1.5. Backup and Restore

Backup your configuration:

# Export all configurations
kubectl get configmap policies -n develocity-provenance-governor -o yaml > policies-backup.yaml
kubectl get configmap properties -n develocity-provenance-governor -o yaml > properties-backup.yaml
kubectl get secret secrets -n develocity-provenance-governor -o yaml > secrets-backup.yaml
kubectl get secret license -n develocity-provenance-governor -o yaml > license-backup.yaml

Restore from backup:

kubectl apply -f policies-backup.yaml
kubectl apply -f properties-backup.yaml
kubectl apply -f secrets-backup.yaml
kubectl apply -f license-backup.yaml
kubectl rollout restart deployment/api -n develocity-provenance-governor

4.2. Common Issues

The following are some possible common issues, how they are recognized, and what might be needed to resolve them:

4.2.1. Invalid or missing Develocity license

The startup error clearly describes the action needed:

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to bind properties under 'develocity' to com.gradle.cavendish.dv.DevelocityProperties:

    Reason: java.lang.IllegalArgumentException: No develocity.license provided in configuration.

Action:

Update your application's configuration

Ensure that the Develocity Provenance Governor deployment is configured with a valid Develocity license.

4.2.2. Missing Configuration properties

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to bind properties under 'develocity' to com.gradle.cavendish.dv.DevelocityProperties:

    Reason: java.lang.IllegalArgumentException: Develocity Access Key `develocity.instances.main.access-key` must have text

Action:

Update your application's configuration

Double check to see that the property mentioned is spelled correctly and in the configuration.

4.2.3. Access control issues (403 or 401 responses)

Access control issues can be troublesome to track down as the system errors on the side of keeping sensitive information secure.

Follow these troubleshooting steps:

  1. Verify your authentication is working:

    kubectl logs deployment/api -n develocity-provenance-governor | grep 'Identity support enabled'
  2. Check that access control policies are loaded:

    kubectl logs deployment/api -n develocity-provenance-governor | grep 'AccessControl'
  3. Verify resource matchers include trailing wildcards where needed:

    • Correct: dv:my-instance/*

    • Incorrect: dv:my-instance (missing wildcard)

  4. Start with a permissive policy for testing:

    withResources:
      - "*:*/*"  # Allows access to all resources

    Once authentication is working, tighten the policy to specific resources.

  5. Enable debug logging temporarily to see policy evaluation details (consult support for instructions).

4.2.4. Invalid access keys for external integrations (e.g. Develocity and Artifactory)

2025-10-16T17:51:58.967-06:00 ERROR 70284 --- [ctor-http-nio-2] c.g.c.dv.DevelocityAccessTokenManager    : Error refreshing access tokens

org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized: 401 Unauthorized from POST https://develocity.grdev.net/api/auth/token
	...

Again, the logging should be fairly clear in this respect. The above, for example, illustrates Develocity Provenance Governor cannot communicate with Develocity. The corrective action here would be to examine the access key in the configuration that is defined for accessing Develocity and make sure it is correct.

4.2.5. Policies don’t seem to be used

2025-10-16T17:51:58.098-06:00  INFO 70284 --- [  restartedMain] c.g.c.p.PolicyScanDefinitionEvaluator    : Policy Scan support enabled for [java-21-toolchain] including policy [none-match-oracle-java-toolchain]
2025-10-16T17:51:58.098-06:00  INFO 70284 --- [  restartedMain] c.g.c.p.PolicyScanDefinitionEvaluator    : Policy Scan support enabled for [java-21-toolchain] including policy [must-match-bellsoft-java-toolchain]
2025-10-16T17:51:58.099-06:00  INFO 70284 --- [  restartedMain] c.g.c.p.PolicyScanDefinitionEvaluator    : Policy Scan support enabled for [java-21-toolchain] including policy [purls-none-match-lombok]
2025-10-16T17:51:58.099-06:00  INFO 70284 --- [  restartedMain] c.g.c.p.PolicyScanDefinitionEvaluator    : Policy Scan support enabled for [java-21-toolchain] including policy [none-match-purl-spring-beans-6.2.7]
2025-10-16T17:51:58.100-06:00  INFO 70284 --- [  restartedMain] c.g.c.p.PolicyScanDefinitionEvaluator    : Policy Scan support enabled for [java-21-toolchain] including policy [must-match-repositories]

When evaluating policies, check startup logs to see which policies loaded. Each enabled policy scan logs its included policies. If your expected policy is missing:

  1. Check the policy name matches exactly (case-sensitive)

  2. Verify policy labels match the scan’s label selector

  3. Confirm the policy YAML is in the 'policies' ConfigMap:

    kubectl get configmap policies -n develocity-provenance-governor -o yaml
  4. Look for parsing errors in earlier log messages:

    kubectl logs deployment/api -n develocity-provenance-governor | grep -i error

4.2.6. Policy Writing and Debugging

My policy doesn’t match anything

Symptoms: Policy scan shows PASSED for a none-match policy even though the forbidden item exists, or FAILED for a must-match policy even though the required item exists.

Common Causes:

  1. Incorrect pURL format - Check the exact format of the dependency in attestations:

    # View attestations to see exact pURL format
    curl -H "Authorization: Basic ..." \
      "https://provenance-governor.example.com/packages/maven/com.example/my-app/1.0.0/attestations"
  2. Case sensitivity - Package names and vendors are case-sensitive:

    • Wrong: pkg:maven/Org.Example/artifact

    • Right: pkg:maven/org.example/artifact

  3. Version mismatch - If you specify a version in the pURL, it must match exactly:

    • pkg:maven/org.example/artifact@1.0.0 does NOT match 1.0.0-SNAPSHOT

    • Use wildcards if you want to match all versions: pkg:maven/org.example/artifact

  4. Namespace separator - For Maven, use / not .:

    • Wrong: pkg:maven/org.example.subpackage/artifact

    • Right: pkg:maven/org.example/subpackage-artifact

Understanding why a policy failed

To debug policy failures:

  1. Check the policy scan results - The failure message includes which attestation values caused the failure:

    {
      "policyName": "must-match-java-21",
      "status": "FAILED",
      "message": "Found Java vendor: Oracle Corporation, version: 17.0.8",
      "remediation": "Update to Java 21"
    }
  2. Review attestation data - Compare what the policy expects vs. what’s in the attestations:

    # Get all attestations for the package
    curl -H "Authorization: Basic ..." \
      "https://provenance-governor.example.com/packages/maven/com.example/my-app/1.0.0/attestations" | jq .
  3. Check predicate types - Ensure your policy operates on the correct predicate type:

Policy is not being evaluated

Symptoms: Policy exists but doesn’t appear in scan results.

Troubleshooting steps:

  1. Verify policy is loaded:

    kubectl logs deployment/api -n develocity-provenance-governor | grep "Policy Scan support enabled"
  2. Check label selectors match:

    Your policy must have labels that match the PolicyScanDefinition selector:

    Policy:

    metadata:
      name: my-policy
      labels:
        policy.my-corp.com/gate: build

    PolicyScanDefinition:

    spec:
      policy-selector:
        match-labels:
          policy.my-corp.com/gate: build  # Must match!
  3. Verify YAML syntax:

    # Check for YAML parsing errors
    kubectl get configmap policies -n develocity-provenance-governor -o yaml
Testing policies before deployment

Before deploying policies to production:

  1. Start with none-match policies - These are easier to debug because they fail when unexpected items are found.

  2. Test against known packages - Run scans against packages you’ve already built and know the contents of.

  3. Use descriptive names - Name policies clearly: block-oracle-jdk is better than java-policy-1.

  4. Add detailed remediation - Include specific steps in the remediation field to help developers fix issues.

  5. Test both pass and fail cases - Verify the policy fails when it should and passes when it should.

4.3. Additional Help

If you experience issues with Develocity Provenance Governor or related components, please submit a support ticket at support.gradle.com, including details of the issue and an attached support bundle from Develocity, if applicable.

Appendix A: API Reference

A.1. Publish

Creates attestations from build information and published them to the attestation store.

A.1.1. Path

  • /packages/{type}/{namespace}/{name}/{version}/attestations

  • /packages/{type}/{name}/{version}/attestations (when namespace is absent)

Path Variables
  • {type}: The package type (e.g., oci, maven)

  • {namespace}: The package namespace or organization (may be omitted)

  • {name}: The package name

  • {version}: The package version

A.1.2. Consumes

  • application/json

  • application/x-www-form-urlencoded

  • multipart/form-data

A.1.3. Produces

  • application/json (Buffered: Returns a JSON array containing all results)

  • application/x-ndjson (Streaming: Returns Newline Delimited JSON objects as they become available)

A.1.4. Request Body

The request body must be a JSON object (for application/json) or form fields (for form-encoded/multipart) matching the following structure:

{
  "sha256": "73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497", (1)
  "repositoryUrl": "artifactory.example.com/maven-repo-local", (2)
  "buildScan": {
    "ids": [ (3)
      "1234567890aaaa",
      "22222bbb"
    ],
    "queries": [ (4)
      "project:my-app value:\"CI Provider=GitHub Actions\" tag:CI tag:3.4.x"
    ]
  }
}
1 The SHA-256 hash of the package.
2 The repository the package is located in.
3 A list of Build Scan IDs to retrieve information from. Optional.
4 A list of queries to retrieve Build Scan data, using the Develocity advanced search syntax. Optional.

A.1.5. Responses

  • 200 OK: Returns PolicyScan.Result objects.

    • If Accept: application/json (default): Returns a JSON array of results.

    • If Accept: application/x-ndjson: Returns a stream of newline-delimited JSON objects.

  • 400 Bad Request: The request content or criteria is invalid.

  • 401 Unauthorized: The credential information presented (if any) is not allowed access to this endpoint.

  • 403 Forbidden: The endpoint is accessible (authorized) but access to the resource requested is disallowed.

  • 500 Internal Server Error: An error occurred during attestation publishing.

A.1.6. Example Request

curl --verbose --request POST "https://provenance-governor.example.com/packages/oci/testifact/1.0/attestations" \
     --header "Content-Type: application/json" \
     --header "Accept: application/json, application/problem+json" \
     --header "Authorization: Basic $(echo -n 'admin:password' | base64)" \
     --data '
{
  "sha256": "df9b564df8de07153224faa58da743ca6bc281610a6dbbec772535de19bd06e2",
  "repositoryUrl": "repo.example.com/docker",
  "buildScan": {
    "ids": [
      "bjvognekiphus"
    ],
    "queries": [
    ]
  }
}
Form URL-Encoded Request (OCI Package)
curl --verbose --request POST "https://provenance-governor.example.com/packages/oci/testifact/1.0/attestations" \
     --header "Content-Type: application/x-www-form-urlencoded" \
     --header "Accept: application/json, application/problem+json" \
     --header "Authorization: Basic $(echo -n 'admin:password' | base64)" \
     --data-urlencode "sha256=df9b564df8de07153224faa58da743ca6bc281610a6dbbec772535de19bd06e2" \
     --data-urlencode "repositoryUrl=repo.example.com/docker" \
     --data-urlencode "buildScan.ids=bjvognekiphus"
Multipart Form Data Request (OCI Package)
curl --verbose --request POST "https://provenance-governor.example.com/packages/oci/testifact/1.0/attestations" \
     --header "Accept: application/json, application/problem+json" \
     --header "Authorization: Basic $(echo -n 'admin:password' | base64)" \
     --form "sha256=df9b564df8de07153224faa58da743ca6bc281610a6dbbec772535de19bd06e2" \
     --form "repositoryUrl=repo.example.com/docker" \
     --form "buildScan.ids=bjvognekiphus"
JSON Request (Maven Package)
curl --verbose --request POST "https://provenance-governor.example.com/packages/maven/com.example/my-maven-app/1.0.0/attestations" \
     --header "Content-Type: application/json" \
     --header "Accept: application/json, application/problem+json" \
     --header "Authorization: Basic $(echo -n 'admin:password' | base64)" \
     --data '
{
  "sha256": "df9b564df8de07153224faa58da743ca6bc281610a6dbbec772535de19bd06e2",
  "repositoryUrl": "https://repo.example.com/maven2",
  "buildScan": {
    "ids": [
      "bjvognekiphus"
    ],
    "queries": [
    ]
  }
}
Form URL-Encoded Request (Maven Package)
curl --verbose --request POST "https://provenance-governor.example.com/packages/maven/com.example/my-maven-app/1.0.0/attestations" \
     --header "Content-Type: application/x-www-form-urlencoded" \
     --header "Accept: application/json, application/problem+json" \
     --header "Authorization: Basic $(echo -n 'admin:password' | base64)" \
     --data-urlencode "sha256=df9b564df8de07153224faa58da743ca6bc281610a6dbbec772535de19bd06e2" \
     --data-urlencode "repositoryUrl=https://repo.example.com/maven2" \
     --data-urlencode "buildScan.ids=bjvognekiphus"
Multipart Form Data Request (Maven Package)
curl --verbose --request POST "https://provenance-governor.example.com/packages/maven/com.example/my-maven-app/1.0.0/attestations" \
     --header "Accept: application/json, application/problem+json" \
     --header "Authorization: Basic $(echo -n 'admin:password' | base64)" \
     --form "sha256=df9b564df8de07153224faa58da743ca6bc281610a6dbbec772535de19bd06e2" \
     --form "repositoryUrl=https://repo.example.com/maven2" \
     --form "buildScan.ids=bjvognekiphus"

A.1.7. Example Response

{
  "status": "SUCCESS",
    "details": {
    "store": "attestation-store-1"
  }
}

A.2. Evaluate

A.2.1. Path

  • /packages/{type}/{namespace}/{name}/{version}/policy-scans/{policy-scan-name}

  • /packages/{type}/{name}/{version}/policy-scans/{policy-scan-name} (when namespace is absent)

Path Variables
  • {type}: The package type (e.g., oci, maven)

  • {namespace}: The package namespace or organization (may be omitted for some package types)

  • {name}: The package name

  • {version}: The package version

  • {policy-scan-name}: The name of the policy scan to execute

A.2.2. Consumes

  • application/json

  • application/x-www-form-urlencoded

  • multipart/form-data

A.2.3. Produces

  • application/json (Buffered: Returns a JSON array containing all results)

  • application/x-ndjson (Streaming: Returns Newline Delimited JSON objects as they become available)

A.2.4. Request Body

The request body must be a JSON object matching the following structure:

{
  "sha256": "string", (1)
  "repositoryUrl": "string" (2)
}
1 The SHA-256 hash of the package.
2 The repository the package is located in.

A.2.5. Responses

Success Response
  • 200 OK: Returns PolicyScan.Result objects.

    • If Accept: application/json (default): Returns a JSON array of results.

    • If Accept: application/x-ndjson: Returns a stream of newline-delimited JSON objects.

Error Responses

All error responses return an RFC 7807 ProblemDetail JSON object with the following structure:

  • 400 Bad Request: The request content or criteria is invalid.

    {
      "type": "about:blank",
      "title": "Bad Request",
      "status": 400,
      "detail": "Invalid request content. Bad criteria (sha256 or repositoryUrl)."
    }
  • 401 Unauthorized: Authentication not allowed.

    {
      "type": "about:blank",
      "title": "Unauthorized",
      "status": 401,
      "detail": "Full authentication is required to access this resource"
    }
  • 403 Forbidden: Access to specific resource disallowed.

    {
      "type": "about:blank",
      "title": "Forbidden",
      "status": 403,
      "detail": "Access to this resource is forbidden"
    }
  • 404 Not Found: The specified policy scan definition was not found.

    {
      "type": "about:blank",
      "title": "Not Found",
      "status": 404,
      "detail": "Policy scan 'build-gate' not found"
    }
  • 429 Too Many Requests: The service is overloaded. This occurs when the connection pool is exhausted due to high concurrency.

    {
      "type": "about:blank",
      "title": "Too Many Requests",
      "status": 429,
      "detail": "The service is experiencing too many requests. Please try again later."
    }

    When receiving a 429 response, implement exponential backoff retry logic. The connection pool exhaustion typically resolves within seconds as in-flight requests complete.

  • 500 Internal Server Error: An error occurred communicating with an external service (Develocity, Artifactory, S3, etc.).

    {
      "type": "about:blank",
      "title": "Internal Server Error",
      "status": 500,
      "detail": "An error occurred while trying to communicate with an external service: Connection refused"
    }

A.2.6. Example Request

JSON Request (OCI Package)
curl --request POST \
  --url https://provenance-governor.example.com/packages/oci/my-app/1.0.0/policy-scans/build \
  --header 'authorization: Basic ***********=' \
  --header 'content-type: application/json' \
  --data '{
  "sha256": "3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91",
  "repositoryUrl": "example.com/docker"
}'
Form URL-Encoded Request (OCI Package)
curl --request POST \
  --url https://provenance-governor.example.com/packages/oci/my-app/1.0.0/policy-scans/build \
  --header 'authorization: Basic ***********=' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data-urlencode "sha256=3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91" \
  --data-urlencode "repositoryUrl=example.com/docker"
Multipart Form Data Request (OCI Package)
curl --request POST \
  --url https://provenance-governor.example.com/packages/maven/com.example/my-maven-app/1.0.0/policy-scans/build \
  --header 'authorization: Basic ***********=' \
  --form "sha256=3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91" \
  --form "repositoryUrl=example.com/docker"
JSON Request (Maven Package)
curl --request POST \
  --url https://provenance-governor.example.com/packages/maven/com.example/my-maven-app/1.0.0/policy-scans/build \
  --header 'authorization: Basic ***********=' \
  --header 'content-type: application/json' \
  --data '{
  "sha256": "3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91",
  "repositoryUrl": "https://repo.example.com/maven2"
}'
Form URL-Encoded Request (Maven Package)
curl --request POST \
  --url https://provenance-governor.example.com/packages/maven/com.example/my-maven-app/1.0.0/policy-scans/build \
  --header 'authorization: Basic ***********=' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data-urlencode "sha256=3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91" \
  --data-urlencode "repositoryUrl=https://repo.example.com/maven2"
Multipart Form Data Request (Maven Package)
curl --request POST \
  --url https://provenance-governor.example.com/packages/maven/com.example/my-maven-app/1.0.0/policy-scans/build \
  --header 'authorization: Basic ***********=' \
  --form "sha256=3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91" \
  --form "repositoryUrl=https://repo.example.com/maven2"

A.2.7. Example Response

[
  {
    "labels": {
      "policy.my-corp.com/gate": "build"
    },
    "status": "unsatisfied",
    "policyUri": "/policies/ResolvedDependenciesRepositories/disallow-untrusted-repos",
    "policyDescription": "Disallow untrusted Maven repositories.",
    "policyRemediation": "Remove 'https://untrusted.repo.com/maven2' from your build's dependency repositories.",
    "attestationUri": "/packages/maven/com.example/my-app/1.0.0/sha256:73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497/attestations/s3:prod/uuid",
    "sourcedFromUri": "https://develocity.example.com/s/abcdef1234567",
    "details": {
      "uris": ["https://untrusted.repo.com/maven2"]
    }
  },
  {
    "labels": {
      "policy.my-corp.com/gate": "build"
    },
    "status": "unsatisfied",
    "policyUri": "/policies/PackageUrl/disallow-vulnerable-dependency",
    "policyDescription": "Disallow usage of specific vulnerable package versions.",
    "policyRemediation": "Upgrade 'pkg:maven/org.example/bad-lib@1.0.0' to a non-vulnerable version (e.g., 1.0.1 or higher).",
    "attestationUri": "/packages/maven/com.example/my-app/1.0.0/sha256:73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497/attestations/s3:prod/uuid",
    "sourcedFromUri": "https://develocity.example.com/s/abcdef1234567",
    "details": {
      "vulnerablePackage": "pkg:maven/org.example/bad-lib@1.0.0",
      "predicateType": "https://cyclonedx.org/schema/sbom/v1.4"
    }
  },
  {
    "labels": {
      "policy.my-corp.com/gate": "build"
    },
    "status": "satisfied",
    "policyUri": "/policies/TrustedPublicKeys/release-pipeline-signer",
    "policyDescription": "Verify all attestations are signed by the release pipeline's trusted key.",
    "policyRemediation": "Ensure the attestation is signed by a trusted key from the release pipeline. Re-run the build if necessary.",
    "attestationUri": "/packages/maven/com.example/my-app/1.0.0/sha256:73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497/attestations/s3:prod/uuid",
    "sourcedFromUri": null,
    "details": {
      "keyIds": ["abcd1234"]
    }
  },
  {
    "labels": {
      "policy.my-corp.com/gate": "build"
    },
    "status": "not-applicable",
    "policyUri": "/policies/JavaToolchains/disallow-jdk8",
    "policyDescription": "Disallow Java 8 toolchains in new builds.",
    "policyRemediation": "Update build configuration to use Java 11 or higher.",
    "attestationUri": "/packages/maven/com.example/my-app/1.0.0/sha256:73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497/attestations/s3:prod/uuid",
    "sourcedFromUri": "https://develocity.example.com/s/abcdef1234567",
    "details": {
      "message": "Attestation does not contain 'https://gradle.com/attestation/java-toolchains/v1' predicate. Policy is not applicable."
    }
  },
  {
    "labels": {
      "policy.my-corp.com/gate": "build"
    },
    "status": "satisfied",
    "policyUri": "/policies/BuildTool/only-gradle-builds",
    "policyDescription": "Ensure only Gradle is used as the build tool.",
    "policyRemediation": "Use Gradle as the build tool. Consult migration guides if necessary.",
    "attestationUri": "/packages/maven/com.example/my-app/1.0.0/sha256:73f482a2296dcc654c380979aae3916a1925ff906b1c43a0659f97fd56e44497/attestations/s3:prod/uuid",
    "sourcedFromUri": "https://develocity.example.com/s/abcdef1234567",
    "details": {
      "buildTool": "Gradle"
    }
  }
]

Response Fields:

  • labels - Policy labels used for organizing and selecting policies

  • status - Result status: satisfied, unsatisfied, or not-applicable

  • policyUri - URI identifying the policy that was evaluated

  • policyDescription - Human-readable description of what the policy checks

  • policyRemediation - Guidance on how to fix policy violations (empty if status is satisfied)

  • attestationUri - URI of the attestation that was evaluated against this policy (may be null)

  • sourcedFromUri - URI to the source of the data that produced the attestation (may be null)

  • details - Policy-specific additional information about the evaluation result

A.3. Fetch Attestation by ID

Retrieves a specific attestation by its unique identifier.

A.3.1. Path

  • /packages/{type}/{namespace}/{name}/{version}/{digestType}:{digest}/attestations/{instance}/{id}

  • /packages/{type}/{name}/{version}/{digestType}:{digest}/attestations/{instance}/{id} (when namespace is absent)

Path Variables
  • {type}: The package type (e.g., oci, maven)

  • {namespace}: The package namespace or organization (may be omitted)

  • {name}: The package name

  • {version}: The package version

  • {digestType}: The digest algorithm (e.g., sha256)

  • {digest}: The digest value

  • {instance}: The storage instance identifier (e.g., s3:prod-bucket, af:dev)

  • {id}: The unique identifier of the attestation

Instance Identifier Format: The instance identifier follows the format {store-type}:{instance-name}, where:

  • store-type is either s3 or af

  • instance-name matches the configured instance name in your application configuration

Examples: s3:prod-bucket, s3:local-s3, af:main, af:compliance-repo

A.3.2. Produces

  • application/json (Buffered: Returns a JSON array containing the attestation)

  • application/x-ndjson (Streaming: Returns Newline Delimited JSON objects)

A.3.3. Responses

Success Response
  • 200 OK: Returns the attestation envelope as a DSSE-wrapped in-toto Statement.

Error Responses

All error responses return an RFC 7807 ProblemDetail JSON object:

  • 400 Bad Request: The request parameters are invalid (e.g., malformed digest, invalid instance identifier).

    {
      "type": "about:blank",
      "title": "Bad Request",
      "status": 400,
      "detail": "Invalid request content. Bad criteria (sha256 or repositoryUrl)."
    }
  • 401 Unauthorized: Authentication not allowed.

    {
      "type": "about:blank",
      "title": "Unauthorized",
      "status": 401,
      "detail": "Full authentication is required to access this resource"
    }
  • 403 Forbidden: Access to the resource is disallowed (user lacks read permissions for this package/repository).

    {
      "type": "about:blank",
      "title": "Forbidden",
      "status": 403,
      "detail": "Access to this resource is forbidden"
    }
  • 404 Not Found: The attestation was not found (either the ID doesn’t exist or the storage instance is not configured).

    {
      "type": "about:blank",
      "title": "Not Found",
      "status": 404,
      "detail": "No attestation found matching request."
    }

A.3.4. Example Request

curl --request GET \
  --url https://provenance-governor.example.com/packages/maven/com.example/my-lib/1.0.0/sha256:abc123.../attestations/s3:prod-bucket/123e4567-e89b-12d3-a456-426614174000 \
  --header 'authorization: Basic ***********=' \
  --header 'accept: application/json'

A.3.5. Example Response

The response is a DSSE (Dead Simple Signing Envelope) containing the attestation.

{
  "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGVUeXBlIjoiaHR0cHM6Ly9ncmFkbGUuY29tL2F0dGVzdGF0aW9uL2J1aWxkLXRvb2wvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicGtnOm1hdmVuL2NvbS5leGFtcGxlL215LWxpYkAxLjAuMCIsImRpZ2VzdCI6eyJzaGEyNTYiOiJhYmMxMjMuLi4ifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRUb29sIjp7Im5hbWUiOiJHcmFkbGUiLCJ2ZXJzaW9uIjoiOC41In19fQ==", (1)
  "payloadType": "application/vnd.in-toto+json", (2)
  "signatures": [ (3)
    {
      "keyid": "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA",
      "sig": "MEUCIQDx8VjW7UXqBVZFxJe9KQOPQmQr6iXzOK7/dZqN8VQG2AIgKTYDLT0Kv1X5mH0Eo7w3xA4MF7B9j3jV8H2q5YN4R0w="
    }
  ]
}
1 Base64-encoded payload: When decoded, this contains an in-toto Statement with the attestation’s predicate (e.g., build tool information, dependencies, etc.)
2 Payload type: Indicates the payload is an in-toto Statement in JSON format
3 Signatures: Cryptographic signatures verifying the attestation’s authenticity

Understanding the DSSE Envelope Structure:

The returned JSON is a DSSE (Dead Simple Signing Envelope) that wraps the attestation:

  1. payload: Base64-encoded in-toto Statement containing:

    • _type: Always https://in-toto.io/Statement/v1

    • predicateType: The type of attestation (e.g., build tool, dependencies, SLSA provenance)

    • subject: The package being attested about

    • predicate: The actual attestation data (varies by predicate type)

  2. payloadType: Always application/vnd.in-toto+json for in-toto Statements

  3. signatures: One or more cryptographic signatures with:

    • keyid: Identifier of the signing key (SHA256 fingerprint)

    • sig: Base64-encoded signature

To extract the attestation data, decode the payload field from base64, then parse the JSON to access the predicate field.

Appendix B: Attestation Reference

Develocity Provenance Governor publishes several attestations to Artifactory’s evidence store. All attestations are in-toto attestations.

The following predicate types are published, when the source Build Scan contains the relevant data:

Each section below describes the predicate payload (the JSON object found in the predicate field of an in-toto Statement whose predicateType matches the given URI). The examples show only the predicate object (not the full in-toto Statement wrapper) for brevity.

B.1. Build Tool Predicate

Describes the build tool that ran the build.

Fields:

  • buildId (string) - The build ID, used to identify the build scan.

  • buildScanUri (URI string) - Link to the Develocity Build Scan that produced this attestation.

  • toolType (string) - Build tool type. Typical values: gradle, maven (future tools may appear).

  • toolVersion (string) - Version of the build tool (e.g. 8.10, 3.9.6, etc.).

  • agentVersion (string) - Version of the Develocity build agent / plugin that captured data.

{
  "buildId": "g4b7d8c1m2",
  "buildScanUri": "https://develocity.example.com/s/g4b7d8c1m2",
  "toolType": "gradle",
  "toolVersion": "8.10",
  "agentVersion": "3.17"
}

B.2. Java Toolchains Predicate

Captures the Java toolchains used during the build.

Fields:

  • buildScanUri (URI string) - Link to the Build Scan.

  • toolchains (array of objects) - List of observed toolchains.

    • vendor (string) - Vendor identifier/name (e.g. Eclipse Adoptium, Oracle Corporation).

    • versions (array of strings) - List of Java runtime versions used (e.g. 17.0.8).

{
  "buildScanUri": "https://develocity.example.com/s/g4b7d8c1m2",
  "toolchains": [
    {
      "vendor": "Eclipse Adoptium",
      "versions": [
        "17.0.8"
      ]
    }
  ]
}

B.3. Resolved Dependencies Repositories Predicate

Enumerates a repository consulted for dependency resolution. A single attestation will be produced that contains multiple Uniform Resource Identifiers (URIs) for all of the repositories used in the same build.

Fields:

  • buildScanUri (URI string) - Link to the Build Scan.

  • uris (List of URI string) - Repository base URIs from which dependencies were resolved.

{
  "buildScanUri": "https://develocity.example.com/s/g4b7d8c1m2",
  "uris": [
    "https://repo1.maven.org/maven2/",
    "https://repo.gradle.org/gradle/libs-releases-local/",
    "https://jitpack.io/"
  ]
}

B.4. Resolved Dependencies Predicate

Contains the set of resolved third-party dependencies as pURLs.

Fields:

  • buildScanUri (URI string) - Link to the Build Scan for correlation.

  • purls (array of strings) - Distinct, sorted list of pURLs. May be empty.

Example (truncated list):

{
  "buildScanUri": "https://develocity.example.com/s/g4b7d8c1m2",
  "purls": [
    "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.17.1",
    "pkg:maven/org.apache.commons/commons-lang3@3.14.0",
    "pkg:npm/react@18.3.1"
  ]
}

B.5. Verification Summary Predicate

A SLSA Verification Summary Attestation (VSA) that certifies the artifact was verified against a Develocity Provenance Governor Policy Scan.

Fields:

  • verifier (object) - Identifies the entity performing the verification (Develocity Provenance Governor).

  • timeVerified (string) - Timestamp of verification (ISO 8601).

  • resourceUri (string) - URI of the artifact being verified (Package URL).

  • policy (object) - The policy used for verification.

    • uri (string) - URI of the Policy Scan Definition.

    • digest (object) - SHA-256 digest of the policy content.

  • inputAttestations (array of objects) - Attestations used as input for verification.

  • verificationResult (string) - Result of the verification (PASSED or FAILED).

{
  "verifier": {
    "id": "https://provenance-governor.example.com/packages/oci/my-app/1.0.0/policy-scans/build-gate",
    "version": {
      "develocity-provenance-governor": "1.2.0"
    }
  },
  "timeVerified": "2023-10-01T12:00:00Z",
  "resourceUri": "pkg:maven/com.example/test-lib@1.0.0?repository_url=repo.example.com/libs-release&sha256=725a3f94ec1af8830d0f708e7941c233d3cb981a6e943ca9aee5899cceb48383",
  "policy": {
    "uri": "/policies/PolicyScanDefinition/build-gate",
    "digest": {
      "sha256": "725a3f94ec1af8830d0f708e7941c233d3cb981a6e943ca9aee5899cceb48383"
    }
  },
  "inputAttestations": [
    {
      "uri": "/packages/oci/my-app/1.0.0/sha256:1f38c1af0cc5c48fc1d60cb3eb42837ec57ede8385db3018582f7c118cbff5a5/attestations/s3:local-s3/338ec7b1-4a61-5960-8983-b8eef80d9b2b",
      "digest": {
        "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
      }
    }
  ],
  "verificationResult": "PASSED"
}