Pluggable Types
Rekor supports pluggable types (aka different schemas) for entries stored in the transparency log. This will allow you to develop your own manifest type in your preferred formatting style (json|yaml|xml).
Currently supported types
The list of currently supported types and their schema is maintained in the repository.
Base schema
The base schema for all types is modeled off of the schema used by Kubernetes and can be found in openapi.yaml
as #/definitions/ProposedEntry
:
definitions:
ProposedEntry:
type: object
discriminator: kind
properties:
kind:
type: string
required:
- kind
The kind
property is a discriminator that is used to differentiate between different pluggable types. Types can have one or more versions of the schema supported concurrently by the same Rekor instance; an example implementation can be seen in rekord.go
.
Adding support for a new type
To add a new type (called newType
in this example):
Add a new definition in
openapi.yaml
that is a derived type of ProposedEntry (expressed in theallOf
list seen below); for example:newType: type: object description: newType object allOf: - $ref: "#/definitions/ProposedEntry" - properties: version: type: string metadata: type: object additionalProperties: true data: type: object $ref: "pkg/types/newType/newType_schema.json" required: - version - data additionalProperties: false
Note: the
$ref
feature can be used to refer to an externally defined JSON schema document; however it is also permitted to describe the entirety of the type in valid Swagger (aka OpenAPI) v2 format withinopenapi.yaml
.Create a subdirectory under
pkg/types/
with your type name (e.g.newType
) as a new Go packageIn this new Go package, define a struct that implements the
TypeImpl
interface as defined inpkg/types/types.go
:type TypeImpl interface { CreateProposedEntry(context.Context, version string, ArtifactProperties) (models.ProposedEntry, error) DefaultVersion() string SupportedVersions() []string IsSupportedVersion(version string) bool UnmarshalEntry(pe models.ProposedEntry) (EntryImpl, error) }
CreateProposedEntry
creates an instance of a proposed entry based on the specified API version and the provided artifact propertiesDefaultVersion
returns the default API version string across all types (to be used if a caller does not specify an explicit version)SupportedVersions
returns the list of all API version strings that can currently be inserted into the log.IsSupportedVersion
returns a boolean denoting whether the specified version could be inserted into the logUnmarshalEntry
will be called with a pointer to a struct that was automatically generated for the type defined inopenapi.yaml
by the go-swagger tool used by Rekor- This struct will be defined in the generated file at
pkg/generated/models/newType.go
(wherenewType
is replaced with the name of the type you are adding) - This method should return a pointer to an instance of a struct that implements the
EntryImpl
interface as defined inpkg/types/types.go
, or anil
pointer with an error specified
- This struct will be defined in the generated file at
Also, the
Kind
constant must return the exact same string as you named your new type inopenapi.yaml
(e.g. “newType
”)Also in this Go package, provide an implementation of the
EntryImpl
interface as defined inpkg/types/entries.go
:type EntryImpl interface { APIVersion() string // the supported versions for this implementation IndexKeys() ([]string, error) // the keys that should be added to the external index for this entry Canonicalize(ctx context.Context) ([]byte, error) // marshal the canonical entry to be put into the tlog Unmarshal(e models.ProposedEntry) error // unmarshal the abstract entry into the specific struct for this versioned type CreateFromArtifactProperties(context.Context, ArtifactProperties) (models.ProposedEntry, error) Verifier() (pki.PublicKey, error) Insertable() (bool, error) // denotes whether the entry that was unmarshalled has the writeOnly fields required to validate and insert into the log }
APIVersion
should return a version string that identifies the version of the type supported by the Rekor serverIndexKeys
should return a[]string
containing the keys that are stored in the search index that should map to this log entry’s ID.Canonicalize
should return a[]byte
containing the canonicalized contents representing the entry. The canonicalization of contents is important as we should have one record per unique signed object in the transparency log.Unmarshal
will be called with a pointer to a struct that was automatically generated for the type defined inopenapi.yaml
by the go-swagger tool used by Rekor- This method should validate the contents of the struct to ensure any string or cross-field dependencies are met to successfully insert an entry of this type into the transparency log
CreateFromArtifactProperties
returns a proposed entry of this specific entry implementation given the provided artifact propertiesVerifier
returns the verification material that was used to verify the digital signatureInsertable
introspects the entry and determines if the object is sufficiently hydrated to make a new entry into the log. Entry instances that are created by reading the contents stored in the log may not be sufficiently hydrated.
In the Go package you have created for the new type, be sure to add an entry in the
TypeMap
ingithub.com/sigstore/rekor/pkg/types
for your new type in theinit
method for your package. The key for the map is the unique string used to define your type inopenapi.yaml
(e.g.newType
), and the value for the map is the name of a factory function for an instance ofTypeImpl
.func init() { types.TypeMap.Set("newType", NewEntry) }
Add an entry to
pluggableTypeMap
incmd/server/app/serve.go
that provides a reference to your package. This ensures that theinit
function of your type (and optionally, your version implementation) will be called before the server starts to process incoming requests and therefore will be added to the map that is used to route request processing for different types.After adding sufficient unit & integration tests, submit a pull request to
sigstore/rekor
for review and addition to the codebase.
Adding a new version of the Rekord
type
To add new version of the default Rekord
type:
Create a new subdirectory under
pkg/types/rekord/
for the new versionIf there are changes to the Rekord schema for this version, create a new JSON schema document and add a reference to it within the
oneOf
clause inrekord_schema.json
. If there are no changes, skip this step.Provide an implementation of the
EntryImpl
interface as defined inpkg/types/types.go
for the new version.In your package’s
init
method, ensure there is a call toSemVerToFacFnMap.Set()
which provides the link between the valid semver ranges that your package can successfully process and the factory function that creates an instance of a struct for your new version.Add an entry to
pluggableTypeMap
incmd/server/app/serve.go
that provides a reference to the Go package implementing the new version. This ensures that theinit
function will be called before the server starts to process incoming requests and therefore will be added to the map that is used to route request processing for different types.After adding sufficient unit & integration tests, submit a pull request to
sigstore/rekor
for review and addition to the codebase.