gateway

package
v1.29.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package gateway derives, loads and interprets the Jamf Platform gateway's published Jamf Pro and Classic API surface.

The gateway does not expose every Jamf Pro endpoint, and its refusals are not self-describing: an unrouted path answers 403 BAD_PERMISSIONS — byte for byte what a missing API-role privilege answers — so `pro app-installer-titles list` on a platform profile used to send an operator hunting for a grant that could not help.

The source is jamfplatform-go-sdk's published api/, the same place specs/platform/ comes from:

pro_api.json                          the Jamf Pro API as published ON the
                                      gateway (servers: {region}.api.jamfcloud.com/pro)
classic_api_resource_documentation.json  the Classic API, likewise (/proclassic)

Both are complete as of SDK adb8d7b, which whitelisted the remaining 38 jpapi paths: 528 and 273 paths, method-for-method identical to the gateway's own spec drops. Each operation also carries x-required-privileges in the gateway scope vocabulary (`categories:read`, `device-actions:execute`), which is what makes the SDK sufficient on its own — see the note on Scopes below.

These two specs are deliberately NOT in PLATFORM_SDK_SPECS. They describe Jamf Pro APIs this repo already generates from specs/*.yaml, and feeding them to the platform generator would emit a second set of Pro commands from gateway paths. Only presence, method and scope are taken.

Index

Constants

View Source
const (
	AnyMethod  = "*"
	AnySubpath = "**"
)

AnyMethod and AnySubpath are the wildcards a table entry may carry: a method of "*" matches every method, and a terminal "**" segment matches the path it sits under and everything below it.

View Source
const (
	ProPrefix     = "/pro"
	ClassicPrefix = "/proclassic"
)

Gateway path prefixes. A caller-facing Jamf Pro path is rewritten onto one of these by client.rewritePathForGateway before it is sent, so the manifest is keyed the same way — that is the form the runtime has in hand, and it keeps the Pro and Classic namespaces from colliding in one table.

View Source
const CoverageFile = "gateway/coverage.json"

CoverageFile is the committed manifest's path, relative to the specs dir.

View Source
const (
	// ProSpecFile is exported because it is not only a coverage source: it is
	// also the only published spec carrying the App Installers surface, which
	// generator/monolith derives specs/AppInstaller*.yaml from in the same run.
	ProSpecFile = "pro_api.json"
)

Source spec filenames, relative to the drop directory. They match the SDK's api/ filenames exactly, so a refresh is a copy with no mapping to keep in step — the same rule specs/platform/ follows.

Variables

View Source
var SourceFiles = []string{ProSpecFile, classicSpecFile}

SourceFiles is the set sync-gateway-coverage copies out of an SDK checkout.

Functions

func CarryForwardProvenance

func CarryForwardProvenance(cov, prev *Coverage)

CarryForwardProvenance copies provenance from a previously written manifest that this run could not determine for itself. Today that is the SDK revision: it describes where the *specs* came from, so re-deriving from the same unchanged specs must not lose it.

Without this, any re-derivation without JAMFPLATFORM_SDK_PATH blanked the field, and verify-gateway-coverage then reported a stale manifest that was byte-identical apart from the provenance it had just erased. Same reasoning as the Protect backup _meta manifest carrying the previous run's inventory forward rather than blanking it.

A nil prev is a no-op, so a first run records whatever it was given.

func Emit

func Emit(cov *Coverage, entries []Entry, path string) error

Emit writes the runtime coverage table to path as package gateway. The runtime package does not import this one — a generator tree has no business being linked into the shipped binary — so the Level constants are declared on both sides and TestLevelsMatchTheGenerator pins them together.

func NormalisePath

func NormalisePath(p string) string

NormalisePath replaces every path parameter with a positional {}.

func OverrideKeys

func OverrideKeys() (probed, served []string)

OverrideKeys returns the declared override keys, sorted, so a test can assert each still matches something the bundle ships. An entry that stops matching is how a table like this goes stale: nothing in a spec announces that routing has landed, so a stale probedUnserved entry keeps refusing an endpoint that works.

func ProbedReason

func ProbedReason(key string) string

ProbedReason returns the recorded probe for a key, for tests and for the generated runtime table.

func Summary

func Summary(entries []Entry) string

Summary renders a human-readable count per level, for the generator's log.

func Write

func Write(cov *Coverage, path string) error

Write marshals the manifest to path, creating the directory if needed.

Types

type Basis

type Basis string

Basis is the evidence behind an Unserved verdict. It selects the wording of the refusal and nothing else.

const (
	// BasisProbe: a recorded wire probe found the gateway does not route it.
	BasisProbe Basis = "probe"
	// BasisUnpublished: the gateway's published spec does not carry it. It may
	// still be routed today.
	BasisUnpublished Basis = "unpublished"
)

type Coverage

type Coverage struct {
	Note    string  `json:"_note"`
	Sources Sources `json:"sources"`

	// Spec maps a gateway path (ProPrefix/ClassicPrefix + normalised path) to the
	// methods the published spec declares for it. This is the whole basis of a
	// verdict: declared is served, undeclared is refused.
	Spec map[string][]string `json:"spec"`

	// Scopes maps the same key to method → the gateway scopes the operation
	// requires, from x-required-privileges.
	//
	// Nothing consumes these yet. They are the missing input for the Platform 403
	// privilege hint (only Pro appends privilege names to a 403 today), and they
	// are why this package no longer needs the GitOps bundle: the bundle's
	// _permissions/routes.yaml carried exactly this map, and as of SDK adb8d7b the
	// two agree entry for entry — 1352 operations, zero disagreements, none on
	// either side alone.
	Scopes map[string]map[string][]string `json:"scopes"`
}

Coverage is the committed manifest. Written by Extract, read by Load.

func Extract

func Extract(srcDir, sdkCommit string) (*Coverage, error)

Extract reads the two specs under srcDir and derives the manifest. sdkCommit is optional provenance, recorded verbatim.

func Load

func Load(path string) (*Coverage, error)

Load reads a manifest written by Write. A missing file is not an error: it returns (nil, nil), so a tree with no manifest generates commands with no gateway verdicts rather than failing — the manifest is committed, but the generator has to keep working for anyone who has not synced the specs.

func (*Coverage) Verdict

func (c *Coverage) Verdict(method, path string) Verdict

Verdict answers for one operation. method is the HTTP method; path is the gateway-form path (ProPrefix or ClassicPrefix), normalised or not.

A nil Coverage answers Served: no manifest, no verdict. That is deliberate rather than defensive. The manifest is committed, but `make generate` has to keep working in a tree where nobody has synced the specs, and the honest answer there is "unknown", which must not refuse anything.

func (*Coverage) VerdictSubtree

func (c *Coverage) VerdictSubtree(path string) Verdict

VerdictSubtree answers for a whole subtree rather than one endpoint: does the gateway carry anything at or beneath path?

This is how a Classic resource is judged, and the exact-path form is wrong for it. Five Classic resources have no bare collection endpoint at all — computerhistory, computerapplications, mobiledevicehistory, patchavailabletitles and patchreports are reachable only as /computerhistory/id/{} and friends — so asking about /proclassic/computerhistory answered "absent" for five resources the gateway serves perfectly well.

func (*Coverage) VerdictSubtreeMethod

func (c *Coverage) VerdictSubtreeMethod(path, method string) Verdict

VerdictSubtreeMethod answers for one method across a whole subtree: does the gateway declare that method on any path at or beneath path?

This is the granularity a Classic *subcommand* needs, and VerdictSubtree is too coarse for it. A Classic resource is judged as a whole because its paths are assembled at runtime from the resource path plus whichever lookup is in play — but the METHOD each subcommand sends is fixed at generate time, and a method the gateway declares nowhere beneath the resource cannot work under any lookup. Classic API 11.28.0 withdrew every read on patchsoftwaretitles while keeping POST /patchsoftwaretitles/id/{}, so the resource is served and `list`, `get`, `update` and `delete` are all dead — a shape the whole-resource verdict reports as fine.

type Entry

type Entry struct {
	Method string
	Path   string
	Level  Level
	Basis  Basis
	Detail string
}

Entry is one operation the gateway is not known to serve, in the form the runtime needs: a gateway-form path with {} wildcards, plus the method it applies to and the evidence behind it.

func Apply

func Apply(cov *Coverage, ops []Op) []Entry

Apply stamps a verdict onto every operation of every parsed resource and returns the entries worth carrying into the binary.

modern takes the Pro prefix and classic the Classic one; a caller passes accessor closures rather than the parser types so this package stays free of an import cycle with generator/parser (which will import it).

type Level

type Level string

Level is whether the Jamf Platform gateway serves an operation.

const (
	// Served means the gateway's published surface carries it. It is also the
	// answer when there is no manifest to consult.
	Served Level = ""

	// Unserved means it is not part of that surface, and a gateway profile is
	// refused before a request is sent.
	//
	// The gateway currently routes some endpoints its published spec omits, and
	// that is transitional: the deployed route set is being narrowed onto the
	// published surface. So "it works today" is not a reason to allow it —
	// allowing it means a workflow keeps being built on an endpoint that is
	// going away, and the failure then arrives as a breakage with no warning.
	// Basis records which evidence produced the verdict, because the two want
	// different wording, not different behaviour.
	Unserved Level = "unserved"
)

type Op

type Op struct {
	Method      string
	GatewayPath string
	// Scope is the granularity of the verdict; see Scope.
	Scope Scope
	// Set records the verdict back onto whatever the caller parsed. It takes the
	// whole Verdict rather than its strings because the scopes travel with it —
	// per operation for a modern path, per method for a Classic subtree.
	Set func(Verdict)
}

Op is one operation to be judged, decoupled from the parser types.

type Scope

type Scope int

Scope selects how an Op is judged and how its entry is emitted.

const (
	// ScopeExact is one method on one path — the modern API's shape, where the
	// path is fixed at generate time.
	ScopeExact Scope = iota
	// ScopeSubtree is every method at or beneath the path, emitted as
	// "* <path>/**". A Classic *resource* is judged this way: a Classic command
	// builds its path at runtime from the resource path plus whichever lookup is
	// in play (/id/{}, /name/{}, /serialnumber/{} …), so enumerating op paths at
	// generate time would mean re-deriving the template's own logic and would
	// miss a shape the day one is added.
	ScopeSubtree
	// ScopeSubtreeMethod is one method anywhere at or beneath the path, emitted
	// as "<METHOD> <path>/**". A Classic *subcommand* is judged this way,
	// because the method it sends is fixed even though its path is not: a method
	// the gateway declares nowhere beneath the resource cannot work under any
	// lookup. Without it, a resource that keeps one method and loses the rest
	// reports every subcommand as served — which is what Classic 11.28.0's
	// patchsoftwaretitles withdrawal did.
	ScopeSubtreeMethod
)

type Sources

type Sources struct {
	Pro     SpecSource `json:"pro"`
	Classic SpecSource `json:"classic"`
	// SDKCommit is the jamfplatform-go-sdk revision the specs were copied from,
	// when the sync was given an SDK checkout. Empty when derived straight from
	// the drop directory, which is the case a developer hits after editing a spec
	// by hand — better empty than confidently wrong.
	SDKCommit string `json:"sdkCommit,omitempty"`
}

Sources records what the manifest was derived from, so a stale copy is visible without re-fetching the specs.

type SpecSource

type SpecSource struct {
	File       string `json:"file"`
	Title      string `json:"title"`
	Version    string `json:"version"`
	Paths      int    `json:"paths"`
	Operations int    `json:"operations"`
}

SpecSource identifies one of the source specs.

type Verdict

type Verdict struct {
	Level Level
	Basis Basis
	// Detail is a sentence fragment naming the evidence, e.g. "not declared by
	// the gateway's Jamf Pro API 11.31.0".
	Detail string
	// Scopes are the gateway scopes the operation requires, from the spec's
	// x-required-privileges. Empty for an unserved operation by definition, and
	// legitimately empty for the 44 unauthenticated Jamf Pro endpoints.
	Scopes []string
	// ScopesByMethod is the subtree form of Scopes, set only by VerdictSubtree:
	// a Classic resource is judged as a whole but its scopes are per method
	// (accounts:read for a GET, accounts:update for a PUT), so one flat list
	// would tell a reader that a list needs the delete permission.
	ScopesByMethod map[string][]string
}

Verdict is the answer for one operation, with the evidence that produced it so a user-facing message can state what is known rather than assert.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL