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 or deployed.

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

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 System and:

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

    2. Publishes a Build Scan to Develocity.

    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 Continuous Integration or Continuous Deployment 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 with Kubernetes and Helm deployment options, making it effortless to integrate into modern cloud-native environments.

2.1. Prerequisites

2.1.1. Develocity License

Using Develocity Provenance Governor requires a valid Develocity license. 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”.

You do not need any particular license entitlements to use Develocity Provenance Governor, any valid Develocity license will work.

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 Provenance Governor outside of Kubernetes please open a support ticket and describe your deployment requirements.

2.1.3. Cluster Resources

Ensure the Kubernetes cluster has sufficient resources to run Develocity Provenance Governor. By default, the combined Kubernetes resource requests are 2 CPUs and 6 GiB memory; limits are 6 CPUs and 6 GiB memory. These values are configurable via Helm values.

2.1.4. Artifactory with Evidence Management

Develocity Provenance Governor relies on Artifactory to store and manage provenance data. It requires network access to an Artifactory instance.

This Artifactory instance must have the Evidence Management feature enabled, with support for external evidence. This requires an Artifactory version of 7.104.2 or later, and an Enterprise+ Artifactory license.

Develocity Provenance Governor requires an Artifactory Access Token or Identity Token. It must have Read and Annotate permissions for the repository you wish to use Develocity Provenance Governor with.

2.1.5. Develocity Instance

Develocity Provence 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

Signing keys are used to sign attestations when publishing them, and to validate their signatures when using them to validate policies. Develocity Provenance Governor needs to be configured with a signing key pair in PEM format. The public key of this pair must be configured in the Artifactory instance attestations will be published to.

We support Ed25519, RSA, and Elliptic Curve keys, but recommend using Ed25519 for its better security and performance.

A key pair can be generated using OpenSSL:

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

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

To enable SSL, you need to have a TLS certificate for Develocity Provenance Governor’s hostname. While SSL is technically optional, it is strongly recommended.

2.2. Application Configuration

Configuration for Develocity Provenance Governor is provided via its Helm values.yaml file. There are several sources of configuration:

  • Application properties - a properties file with various application settings.

  • Sensitive application properties - a properties file with sensitive settings, such as access tokens or keys.

  • Access control policies - YAML documents defining access control policies.

  • Policies - YAML documents defining policies to enforce on packages.

2.2.1. 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 a name you choose to identify this Develocity instance.

Provide a Develocity access key in the sensitive application properties:

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

2.2.2. Artifactory

To configure access to an Artifactory instance, 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.

Provide an Artifactory access or ID token in the sensitive application properties:

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

2.2.3. 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.

oapiVersion: 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)
        withClaim: (4)
          organization: "my-org"

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

  withResources: (7)
    - pkg:maven/* (8)
    - dv:my-develocity (9)
    - af:my-artifactory/* (10)
1 Matchers for identities to apply this policy to. May match OIDC 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 Resource matchers.
8 Resource matcher that matches all maven packages.
9 Resource matcher that matches a Develocity instance named my-develocity.
10 Resource matcher that matches all repositories in the Artifactory instance named my-artifactory.

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 resource. They are string matchers that support wildcards. The resources used are:

Resource Type Resource specifier

Packages

The pURL of the package.

Artifactory instance

af:<instance-name/instance-uri>/<repository>

Develocity instance

dv:<instance-name/instance-uri>

<instance-name/instance-uri> means that resource matchers may match either the name you configured for the instance, or the URI of the instance.

Basic identities

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

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.

2.2.4. 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.2.5. Signing Keys

The signing key pair is provided directly as Helm values.

2.3. 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.3.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.3.2. 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. Deploy the core workload (ServiceAccount, Services, Deployment, NetworkPolicy)

  4. Verify rollout & logs

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

  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

2.3.3. Quick Start (Local k3d + Basic HTTP Ingress)

If you just want to stand up a working instance locally with k3d and basic auth (no TLS) follow these condensed steps. Full details remain in later sections.

Details
# 1. Create cluster (port 80 exposed)
brew install k3d
k3d cluster create --port "80:80@loadbalancer"

# 2. Namespace
kubectl create namespace develocity-provenance-governor

# 3. Registry + License secrets
LICENSE=$(echo -n "ge:$(cat ./develocity.license)" | base64 | tr -d '\n')
cat > registry-secret.json <<JSON
{ "auths": { "https://registry.gradle.com": { "auth": "${LICENSE}" } } }
JSON

kubectl create secret docker-registry registry-secret \
  --namespace develocity-provenance-governor \
  --from-file=.dockerconfigjson=./registry-secret.json

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

# 4. Deploy core components (set version + digest)
kubectl --namespace develocity-provenance-governor apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: develocity-provenance-governor
---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api
  namespace: develocity-provenance-governor
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api-http
  namespace: develocity-provenance-governor
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: http
  selector:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api-http-monitoring
  namespace: develocity-provenance-governor
spec:
  ports:
  - name: http-monitoring
    port: 9090
    protocol: TCP
    targetPort: http-monitoring
  selector:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api
  namespace: develocity-provenance-governor
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/component: backend
      app.kubernetes.io/name: api
  template:
    metadata:
      labels:
        app.kubernetes.io/component: backend
        app.kubernetes.io/name: api
    spec:
      containers:
      - image: registry.gradle.com/develocity/provenance-governor:1.0.1@sha256:d50cc7c50a3d02d723f065b3f192274210764ff96fcee6626a0481208bd3d3fa
        imagePullPolicy: IfNotPresent
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 9090
        name: api-http
        ports:
        - containerPort: 8080
          name: http
        - containerPort: 9090
          name: http-monitoring
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 9090
        resources:
          limits:
            cpu: 2000m
            memory: 1Gi
          requests:
            cpu: 2000m
            memory: 1Gi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          seccompProfile:
            type: RuntimeDefault
        volumeMounts:
        - mountPath: /workspace/config/license
          name: license
          readOnly: true
        - mountPath: /workspace/config/secrets
          name: secrets
          readOnly: true
        - mountPath: /workspace/config/policies
          name: policies
          readOnly: true
        - mountPath: /workspace/config/properties
          name: properties
          readOnly: true
        - mountPath: /tmp
          name: tmp
          readOnly: false
      imagePullSecrets:
      - name: registry-secret
      securityContext:
        fsGroup: 65534
        fsGroupChangePolicy: Always
        runAsGroup: 65534
        runAsNonRoot: true
        runAsUser: 65534
        seccompProfile:
          type: RuntimeDefault
      serviceAccountName: api
      volumes:
      - configMap:
          name: policies
          optional: true
        name: policies
      - configMap:
          name: properties
          optional: true
        name: properties
      - name: secrets
        secret:
          optional: true
          secretName: secrets
      - name: license
        secret:
          optional: true
          secretName: license
      - emptyDir: {}
        name: tmp
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api
  namespace: develocity-provenance-governor
spec:
  ingress:
  - from:
    - namespaceSelector: {}
      podSelector: {}
    ports:
    - port: 8080
      protocol: TCP
  podSelector:
    matchLabels:
      app.kubernetes.io/component: backend
      app.kubernetes.io/name: api
  policyTypes:
  - Ingress
EOF

# 5. Verify rollout
kubectl rollout status deployment/api --namespace develocity-provenance-governor --timeout=90s

# 6. Check logs
kubectl logs deployment/api --namespace develocity-provenance-governor | head -n 50

# 7. (Optional) Basic HTTP Ingress (nip.io for local resolution)
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

# 8. Test route (expect 401 Unauthorized – no credentials yet)
curl -i api.127.0.0.1.nip.io

Move on to the detailed sections below to extend functionality (authentication, policies, signing, external integrations).

2.3.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.3.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.

LICENSE=$(echo -n "ge:$(cat ./develocity.license)" | base64 | tr -d '\n')
cat <<EOF >./registry-secret.json
{
  "auths": {
    "https://registry.gradle.com": {
    "auth":"${LICENSE}"
    }
  }
}
EOF

kubectl create secret docker-registry registry-secret \
  --namespace develocity-provenance-governor \
  --from-file=.dockerconfigjson=./registry-secret.json
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.3.6. Deploy Core Components

Apply the workload manifests. (See Quick Start)

Details
kubectl --namespace develocity-provenance-governor apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: develocity-provenance-governor
---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api
  namespace: develocity-provenance-governor
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api-http
  namespace: develocity-provenance-governor
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: http
  selector:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api-http-monitoring
  namespace: develocity-provenance-governor
spec:
  ports:
  - name: http-monitoring
    port: 9090
    protocol: TCP
    targetPort: http-monitoring
  selector:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api
  namespace: develocity-provenance-governor
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/component: backend
      app.kubernetes.io/name: api
  template:
    metadata:
      labels:
        app.kubernetes.io/component: backend
        app.kubernetes.io/name: api
    spec:
      containers:
      - image: registry.gradle.com/develocity/provenance-governor:1.0.1@sha256:d50cc7c50a3d02d723f065b3f192274210764ff96fcee6626a0481208bd3d3fa
        imagePullPolicy: IfNotPresent
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 9090
        name: api-http
        ports:
        - containerPort: 8080
          name: http
        - containerPort: 9090
          name: http-monitoring
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 9090
        resources:
          limits:
            cpu: 2000m
            memory: 1Gi
          requests:
            cpu: 2000m
            memory: 1Gi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          seccompProfile:
            type: RuntimeDefault
        volumeMounts:
        - mountPath: /workspace/config/license
          name: license
          readOnly: true
        - mountPath: /workspace/config/secrets
          name: secrets
          readOnly: true
        - mountPath: /workspace/config/policies
          name: policies
          readOnly: true
        - mountPath: /workspace/config/properties
          name: properties
          readOnly: true
        - mountPath: /tmp
          name: tmp
          readOnly: false
      imagePullSecrets:
      - name: registry-secret
      securityContext:
        fsGroup: 65534
        fsGroupChangePolicy: Always
        runAsGroup: 65534
        runAsNonRoot: true
        runAsUser: 65534
        seccompProfile:
          type: RuntimeDefault
      serviceAccountName: api
      volumes:
      - configMap:
          name: policies
          optional: true
        name: policies
      - configMap:
          name: properties
          optional: true
        name: properties
      - name: secrets
        secret:
          optional: true
          secretName: secrets
      - name: license
        secret:
          optional: true
          secretName: license
      - emptyDir: {}
        name: tmp
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  labels:
    app.kubernetes.io/component: backend
    app.kubernetes.io/name: api
    app.kubernetes.io/part-of: develocity-provenance-governor
    app.kubernetes.io/version: 1.0.1
  name: api
  namespace: develocity-provenance-governor
spec:
  ingress:
  - from:
    - namespaceSelector: {}
      podSelector: {}
    ports:
    - port: 8080
      protocol: TCP
  podSelector:
    matchLabels:
      app.kubernetes.io/component: backend
      app.kubernetes.io/name: api
  policyTypes:
  - Ingress
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 ...

2.3.7. 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:
    - api.example.com
    secretName: https-api-tls
  rules:
  - host: api.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   api.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://api.example.com -i

curl https://api.example.com -i
HTTP/2 401
content-length: 0
www-authenticate: Basic realm="Realm"
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: 0
x-content-type-options: nosniff
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: DENY
x-xss-protection: 0
referrer-policy: no-referrer

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

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.3.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.3.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 (fixing prior typo artfiactory):

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 (previous version incorrectly stated RSA).

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

Expected log algorithm: SHA256withRSA (previous version incorrectly stated ECDSA).

2.3.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.3.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.3.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.3.15. 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

Next steps: Integrate with CI/CD to rotate credentials, implement key rotation schedule, and enforce least-privilege policies.

3. Using Develocity Provenance Governor

Once Develocity Provenance Governor is installed and configured, you can start using it to publish attestations and evaluate policies on software packages.

It is recommended to first start by publishing attestations for software packages. Once you are confident that attestations are being published correctly, you can start writing policies and evaluating them against software packages.

3.1. Publishing Attestations

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

In both cases, the following information will be needed:

  • Identifiers for the software package (which is the "subject" of the attestations):

    • The type of the software package (for example, oci for an OCI image, or maven for a Maven package).

    • The name of the software package

    • The version of the software package

    • The SHA-256 digest of the software package

    • The Artifactory repository URL where the software package was published

  • A list of Build Scan identifiers from Develocity that were used to build the software package, or an advanced query to find the associated build in formation in Develocity.

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

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

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

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

3.1.1. 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-toolchain/v1

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

Resolved Dependency Repository

https://gradle.com/attestation/resolved-dependency-repository/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 can only be applied to the attestation types generated by Develocity Provenance Governor.

Unlike other policy engines, Develocity Provenance Governor policies are defined declaratively using YAML and are loaded into the system via configuration. This makes it easy to read and write policies, and to version control them alongside your deployment code.

3.2.1. Types of policies

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

Policy Policy Kind Description

Build Tool Policy

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.

Java Toolchain

JavaToolchain

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.

Resolved Dependency Repository

ResolvedDependencyRepository

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.

Package URL

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.

Attestation Signature Verification Policy

TrustedPublicKeys

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

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

3.2.2. General policy structure

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

Polices 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/java-21: enforcement-policies
    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)
    cvss: <critical|high|medium|low> # common for vulnerability management
    severity: <minor|sever|warning> # common for compliance and regulatory enforcement / risk assessment
    dry-run: true # domain specific
    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.3. BuildTool

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

Example: Require Specific Build Tools and Versions
kind: BuildTool
apiVersion: policy.gradle.com/v1
metadata:
  name: example-build-tool-policy
  labels:
    environment: ci
    team: backend
  annotations:
    createdBy: admin
spec:
  resultsLabels:
    severity: high
    category: 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.4. JavaToolchain

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

Example: Disallow Oracle Java Toolchains
apiVersion: policy.gradle.com/v1
kind: JavaToolchain
metadata:
  name: none-match-oracle-java-toolchain
  labels:
    java-21: toolchain
    java-17: toolchain
    owner: DevSecOps
spec:
  labels:
    devsecops.gradle.com/compliance/severity: critical
  description: Disallow Oracle Java Toolchains in builds.
  remediation: Update the Gradle build to use BellSoft Java toolchain.
  matching-strategy: none-match
  toolchains:
  - vendor: oracle
Example: Require BellSoft Java Toolchains
apiVersion: policy.gradle.com/v1
kind: JavaToolchain
metadata:
  name: must-match-bellsoft-java-toolchain
  labels:
    java-21: toolchain
    java-17: toolchain
    owner: DevSecOps
spec:
  labels:
    devsecops.gradle.com/compliance/severity: critical
  description: Builds must use BellSoft Java toolchains in builds.
  remediation: Update the build to use BellSoft Java toolchain.
  matching-strategy: must-match
  toolchains:
  - vendor: BellSoft Liberica
    versions:
    - 21.0.8
    - 21.0.7
    - 21.0.6
    - 21.0.5

3.2.5. ResolvedDependenciesRepositories

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

Example: Allow Only Approved Repositories
apiVersion: policy.gradle.com/v1
kind: ResolvedDependenciesRepositories
metadata:
  name: must-match-repositories
  labels:
    java-21: ""
    java-17: deny-purl
    owner: DevSecOps
spec:
  labels:
    devsecops.gradle.com/compliance/severity: critical
    dry-run: true
  description: Allow resolved dependencies repositories
  remediation: Remove the offending repository from build configuration
  matching-strategy: must-match
  uris:
  - https://artifacts.example.com/maven2
  - https://repo.grdev.net/artifactory/enterprise-libs-release-candidates-local/
  - https://plugins.gradle.org/m2/
  - https://repo.grdev.net/artifactory/public

3.2.6. PackageUrl

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

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

Example: Disallow Lombok Dependency
apiVersion: policy.gradle.com/v1
kind: PackageUrl
metadata:
  name: purls-none-match-lombok
  labels:
    java-21: toolchain
    java-17: toolchain
    owner: DevSecOps
spec:
  labels:
    devsecops.gradle.com/compliance/severity: critical
    dry-run: true
  description: Disallow lombok dependency
  remediation: Remove lombok dependency from project
  matching-strategy: 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:
    java-21: toolchain
    java-17: toolchain
    owner: DevSecOps
spec:
  labels:
    devsecops.gradle.com/compliance/severity: critical
    dry-run: true
  description: Find usages of spring 6.2.7
  remediation: Upgrade dependency to spring 6.2.7
  matching-strategy: none-match
  purls:
  - pkg:maven/org.springframework/*@6.2.7

3.2.7. 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.

Example: TrustedPublicKeys
apiVersion: policy.gradle.com/v1
kind: TrustedPublicKeys
metadata:
  name: trusted-keys
  labels:
    java-21: toolchain
    owner: DevSecOps
spec:
  description: For the Java 21 toolchain, validate attestation signatures
  remediation: Make sure the attestations are signed with the expected key
  keys:
    my-test-key:
      pem: |-
           -----BEGIN PUBLIC KEY-----
           REPLACE THIS LINE WITH THE INTERNALS OFF THE PUBLIC KEY
           -----END PUBLIC KEY-----
    my-testb64-key:
      pemBase64:

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 stored alongside policies.

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 enforcement strategies by grouping related policies together. There are many different ways to organize policy scans. For example, you might create scans based on:

  • The type of software package (for example, OCI or Maven).

  • The environment the package is intended to be deployed to (for example, Development, Staging, or Production).

  • The organizational unit or team responsible for the package (for example, Frontend, or Backend).

  • The department or business function the package supports (for example, Finance, HR, or Marketing).

3.3.1. 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: java-21-toolchain
  labels:
    java-21: toolchain
    owner: DevSecOps
spec:
  description: Policy scan definition for Java 21 toolchain
  policy-selector:
    match-labels:
      java-21: toolchain

The above policy scan will include all policies labeled with java-21: toolchain.

3.4. Integrating with Continuous Integration and Continuous Deployment systems

3.4.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 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://develocity-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://develocity-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.4.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.

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

4. Troubleshooting

4.1. Common Issues

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

4.1.1. Invalid or missing Develocity license

The startup error clearly describe 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.1.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.grdev.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.1.3. Access control issues (403 or 401 responses)

These types of issues can be troublesome to track down as the system errs on the side of keeping sensitive information secure. One strategy to mitigate these sorts of problems is to being with a permissive access control policy and being to lock it down after other integration issues are working. Also, make sure to double check that the trailing wildcards are not missing in the resource identifiers.

4.1.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.1.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, if expected behaviors from policies are not seen, make sure the expected policies are loaded. By looking at the startup logs, the policies loaded will be logged. If the one in question is not in that list, that would be a problem.

4.2. 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

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)
  "develocity": {
    "buildScanIds": [ (3)
      "1234567890aaaa",
      "22222bbb"
    ],
    "buildScanQueries": [ (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 a PublishAttestation.SuccessfulResponse object in JSON format.

  • 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 -v -X POST "http://localhost:8080/packages/oci/testifact/1.0/attestations" \
     -H "Content-Type: application/json" \
     -H "Accept: application/json, application/problem+json" \
     -H "Authorization: Basic $(echo -n 'admin:password' | base64)" \
     -d '
{
  "sha256": "df9b564df8de07153224faa58da743ca6bc281610a6dbbec772535de19bd06e2",
  "repositoryUrl": "develocitytia.jfrog.io/docker-trial",
  "buildScan": {
    "ids": [
      "bjvognekiphus"
    ],
    "queries": [
    ]
  }
}

A.1.7. Example Response

{
  "status": "SUCCESS",
  "attestationUri": "/attestations/789",
  "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

A.2.3. Produces

  • application/json

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

  • 200 OK: Returns a stream (Flux) of PolicyScan.Result objects in JSON format.

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

  • 401 Unauthorized: Authentication not allowed.

  • 403 Forbidden: Access to specific resource disallowed.

  • 404 Not Found: The specified resource was not found.

  • 429 Too Many Requests: The service is overloaded.

  • 500 Internal Server Error: An error occurred communicating with an external service.

A.2.6. Example Request

curl --request POST \
  --url http://localhost:8080/packages/oci/testifact/2.0/policy-scans/java-21-toolchain \
  --header 'authorization: Basic ***********=' \
  --header 'content-type: application/json' \
  --data '{
  "sha256": "3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91",
  "repositoryUrl": "example.com/docker-trial"
}'

A.2.7. Example Response

[
  {
    "status": "unsatisfied",
    "policyUri": "/policies/ResolvedDependenciesRepository/must-match-repositories",
    "policyDescription": "Allow resolved dependencies repositories",
    "policyRemediation": "Remove the offending repository from build configuration",
    "attestationStoreInstance": "af:tia",
    "attestationStoreUri": "https://develocitytia.jfrog.io/artifactory/docker-trial/.evidence/c35e1a4fecb35e4f7a829b2ae3d4bea93ad66247a062dda4b3301df79d73f5f8/3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91/gradle-resolved-dependencies-repository-1759517425992-af494c3b.json",
    "sourcedFromUri": "http://localhost:24300/s/wyqqaj64qu73k",
    "details": {
      "repositoryUri": "https://repo.maven.apache.org/maven2"
    }
  },
  {
    "status": "not-applicable",
    "policyUri": "/policies/PackageUrl/purls-none-match-lombok",
    "policyDescription": "Disallow lombok dependency",
    "attestationStoreInstance": "af:tia",
    "attestationStoreUri": "https://develocitytia.jfrog.io/artifactory/docker-trial/.evidence/c35e1a4fecb35e4f7a829b2ae3d4bea93ad66247a062dda4b3301df79d73f5f8/3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91/gradle-build-tool-1759517425991-dd3a6aab.json",
    "sourcedFromUri": "http://localhost:24300/s/wyqqaj64qu73k",
    "details": {
      "message": "Envelope does not contain a [https://gradle.com/attestation/resolved-dependencies/v1] predicate type."
    }
  },
  {
    "status":"satisfied",
    "policyUri":"/policies/JavaToolchains/none-match-oracle-java-toolchain",
    "policyDescription":"Disallow Oracle Java Toolchains in builds.",
    "attestationStoreInstance":"af:tia",
    "attestationStoreUri":"https://develocitytia.jfrog.io/artifactory/docker-trial/.evidence/fab0e045e6304acb00818728589b9350deb3b3f9d8787b0af984ae1062d35126/3f6cabfb527d740e79e0f49e2f4e564279d8c74a0bfc4481fc3a44e6b085fe91/gradle-java-toolchains-1762553480138-c74a20cb.json",
    "sourcedFromUri": "http://localhost:24300/s/wyqqaj64qu73k",
    "details":{
   }
  }
]

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 Toolchain Predicate

Captures the Java runtime used during the build. Multiple attestations of this predicate type may be produced (one per distinct Java runtime) for the same build.

Fields:

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

  • version (string) - Java runtime version string (e.g. 17.0.8). May be empty if unavailable.

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

{
  "buildScanUri": "https://develocity.example.com/s/g4b7d8c1m2",
  "version": "17.0.8",
  "vendor": "Eclipse Adoptium"
}

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"
  ]
}

Appendix C: Release history

1.0.1

11th November 2025
  • [NEW] Dependency updates

1.0.0

17th October 2025
  • Initial release of Develocity Provenance Governor.