Dynamically querying EndOfLife.date data for internal packages with Open Policy Agent and Dependency Management Data
When working with dependency-management-data, one of the great things it can do is provide insight to how close/far past the end-of-life date of upstream projects your dependencies are.
This data is provided through the excellent EndOfLife.date, and dependency-management-data wires this data in for dependencies it can understand are i.e. Python, Ruby, or Go.
But you're not always going to be working with dependencies that are used in the public world, for instance I've recently been working with some images of the format:
docker.internal.tld/python:3.9
docker.internal.tld/go:1.22-nonroot
Each of these are still bound by the End-of-Life lifecycle of the upstream projects, but right now there's no way for dependency-management-data to be able to surface that information.
I've been looking at a few options for how to surface this better, and fortunately there are a few choices within dependency-management-data, but the one that I was most interested was the excellent Open Policy Agent (OPA) and the inbuilt support that dependency-management-data has for it. I'd recently seen that there's the ability to perform HTTP requests, and thought this would be a good opportunity to see if this would work.
Today I've finished off an example of this policy, alongside a couple of tweaks to DMD to make the process more performant.
This Policy gives us the ability to determine, for each usage of docker.internal.tld/python
:
- is it already End-of-Life?
- is it soon End-of-Life?
- is it already out of active support?
- is it soon out of active support?
Notice that we also explicitly cache the HTTP requests for OPA, as we don't need to retrieve this more than once, and we need to perform some management of the different shapes of the EndOfLife.date API structure.
The policy looks like so:
# NOTE that this was significantly reduced in DMD v0.102.0
package policy
import rego.v1
default the_product := null
default cycle := null
default advisory_type := "DEPRECATED"
days_until_warn := 60
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# below is for the Policy author to determine, as it's dependency specific
version := split(input.dependency.current_version, "-")[0]
versions := split(version, ".")
cycle := sprintf("%s.%s", [versions[0], versions[1]])
# DMD: filter: package_name: docker.internal.tld/python
the_product := "python" if {
input.dependency.package_name == "docker.internal.tld/python"
}
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# below here is boilerplate
deny contains msg if {
[is_eol, msg] := endoflifedate_is_eol(the_product, cycle)
is_eol
}
# we need to override this from the default DEPRECATED
advisory_type := "UNMAINTAINED" if {
[is_eol, msg] := endoflifedate_is_eol(the_product, cycle)
is_eol
}
deny contains msg if {
[approaching_eol, msg] := endoflifedate_approaching_eol(the_product, cycle, days_until_warn)
approaching_eol
}
deny contains msg if {
[is_unsupported, msg] := endoflifedate_is_unsupported(the_product, cycle)
is_unsupported
}
deny contains msg if {
[approaching_unsupported, msg] := endoflifedate_approaching_unsupported(the_product, cycle, days_until_warn)
approaching_unsupported
}
As of v0.102.0 of dependency-management-data, this includes builtins that perform a lot of the boilerplate of determining whether our image is End-of-Life/unsupported, or nearing each case, and instead allowing us to focus on what we're actually trying to convey.