Rekor supports pluggable types (aka different schemas) for entries stored in the transparency log.
- Rekord (default type) schema
- Versions: 0.0.1
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
.
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 package -
In this new Go package, define a struct that implements the
TypeImpl
interface as defined intypes.go
:
type TypeImpl interface {
Kind() string
UnmarshalEntry(pe models.ProposedEntry) (EntryImpl, error)
}
Kind
must return the exact same string as you named your new type inopenapi.yaml
(e.g. "newType
")UnmarshalEntry
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 intypes.go
, or anil
pointer with an error specified
- This struct will be defined in the generated file at
- Also in this Go package, provide an implementation of the
EntryImpl
interface as defined intypes.go
:
type EntryImpl interface {
APIVersion() string
Canonicalize(ctx context.Context) ([]byte, error)
FetchExternalEntities(ctx context.Context) error
HasExternalEntities() bool
Unmarshal(pe models.ProposedEntry) error
}
APIVersion
should return a version string that identifies the version of the type supported by the Rekor serverCanonicalize
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.FetchExternalEntities
should retrieve any entities that make up the entry which were not included in the object provided in the HTTP request to the Rekor serverHasExternalEntities
indicates whether the instance of the struct has any external entities it has yet to fetch and resolveUnmarshal
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
- In the Go package you have created for the new type, be sure to add an entry in the
TypeMap
ingithub.com/projectrekor/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
projectrekor/rekor
for review and addition to the codebase.
To add new version of the default Rekord
type:
-
Create a new subdirectory under
pkg/types/rekord/
for the new version -
If 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
projectrekor/rekor
for review and addition to the codebase.