Skip to content

Commit

Permalink
WIP: Convert SigstoreKeys to protobuf-specs type
Browse files Browse the repository at this point in the history
Signed-off-by: Cody Soyland <[email protected]>
  • Loading branch information
codysoyland committed Mar 7, 2024
1 parent 5f7d45f commit 0975b9e
Show file tree
Hide file tree
Showing 17 changed files with 230 additions and 223 deletions.
4 changes: 2 additions & 2 deletions cmd/tester/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ func main() {

c := &config.SigstoreKeys{}
c.ConvertFrom(context.Background(), tr.Spec.SigstoreKeys)
maps := make(map[string]config.SigstoreKeys, 0)
maps := make(map[string]*config.SigstoreKeys, 0)

maps[tr.Name] = *c
maps[tr.Name] = c
configCtx.SigstoreKeysConfig = &config.SigstoreKeysMap{SigstoreKeys: maps}

ctx = config.ToContext(ctx, configCtx)
Expand Down
2 changes: 1 addition & 1 deletion docs/api-types/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ RFC3161Timestamp specifies the URL to a RFC3161 time-stamping server that holds

| Field | Description | Scheme | Required |
| ----- | ----------- | ------ | -------- |
| trustRootRef | Use the Certificate Chain from the referred TrustRoot.TimeStampAuthorities | string | false |
| trustRootRef | Use the Certificate Chain from the referred TrustRoot.TimestampAuthorities | string | false |

[Back to TOC](#table-of-contents)

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0
github.com/ryanuber/go-glob v1.0.0
github.com/sigstore/cosign/v2 v2.2.3
github.com/sigstore/protobuf-specs v0.3.0
github.com/sigstore/rekor v1.3.5
github.com/sigstore/sigstore v1.8.2
github.com/stretchr/testify v1.9.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,8 @@ github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
github.com/sigstore/cosign/v2 v2.2.3 h1:WX7yawI+EXu9h7S5bZsfYCbB9XW6Jc43ctKy/NoOSiA=
github.com/sigstore/cosign/v2 v2.2.3/go.mod h1:WpMn4MBt0cI23GdHsePwO4NxhX1FOz1ITGB3ALUjFaI=
github.com/sigstore/protobuf-specs v0.3.0 h1:E49qS++llp4psM+3NNVEb+C4AD422bT9VkOQIPrNLpA=
github.com/sigstore/protobuf-specs v0.3.0/go.mod h1:ynKzXpqr3dUj2Xk9O/5ZUhjnpi0F53DNi5AdH6pS3jc=
github.com/sigstore/rekor v1.3.5 h1:QoVXcS7NppKY+rpbEFVHr4evGDZBBSh65X0g8PXoUkQ=
github.com/sigstore/rekor v1.3.5/go.mod h1:CWqOk/fmnPwORQmm7SyDgB54GTJizqobbZ7yOP1lvw8=
github.com/sigstore/sigstore v1.8.2 h1:0Ttjcn3V0fVQXlYq7+oHaaHkGFIt3ywm7SF4JTU/l8c=
Expand Down
156 changes: 74 additions & 82 deletions pkg/apis/config/sigstore_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ package config
import (
"context"
"encoding/json"
"encoding/pem"
"fmt"

"github.com/sigstore/policy-controller/pkg/apis/policy/v1alpha1"
pbcommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
pbtrustroot "github.com/sigstore/protobuf-specs/gen/pb-go/trustroot/v1"
corev1 "k8s.io/api/core/v1"
"knative.dev/pkg/apis"
"sigs.k8s.io/yaml"
)

Expand All @@ -33,54 +35,6 @@ const (
SigstoreKeysConfigName = "config-sigstore-keys"
)

// Note that these are 1:1 mapped to public API SigstoreKeys. Reasoning
// being that we may choose to serialize these differently, or use the protos
// that are defined upstream, so want to keep the public/private distinction, so
// that we can change things independend of breaking the API. Time will tell
// if this is the right call, but we can always reunify them later if we so
// want.
// TODO(vaikas): See about replacing these with the protos here once they land
// and see how easy it is to replace with protos instead of our custom defs
// above.
// https://github.com/sigstore/protobuf-specs/pull/5
// And in particular: https://github.com/sigstore/protobuf-specs/pull/5/files#diff-b1f89b7fd3eb27b519380b092a2416f893a96fbba3f8c90cfa767e7687383ad4R70

// TransparencyLogInstance describes the immutable parameters from a
// transparency log.
// See https://www.rfc-editor.org/rfc/rfc9162.html#name-log-parameters
// for more details.
// The incluced parameters are the minimal set required to identify a log,
// and verify an inclusion promise.
type TransparencyLogInstance struct {
BaseURL apis.URL `json:"baseURL"`
HashAlgorithm string `json:"hashAlgorithm"`
// PEM encoded public key
PublicKey []byte `json:"publicKey"`
LogID string `json:"logID"`
}

type DistinguishedName struct {
Organization string `json:"organization"`
CommonName string `json:"commonName"`
}

type CertificateAuthority struct {
// The root certificate MUST be self-signed, and so the subject and
// issuer are the same.
Subject DistinguishedName `json:"subject"`
// The URI at which the CA can be accessed.
URI apis.URL `json:"uri"`
// The certificate chain for this CA.
// CertChain is in PEM format.
CertChain []byte `json:"certChain"`

// TODO(vaikas): How to best represent this
// The time the *entire* chain was valid. This is at max the
// longest interval when *all* certificates in the chain where valid,
// but it MAY be shorter.
// dev.sigstore.common.v1.TimeRange valid_for = 4;
}

// SigstoreKeys contains all the necessary Keys and Certificates for validating
// against a specific instance of Sigstore.
// TODO(vaikas): See about replacing these with the protos here once they land
Expand All @@ -90,24 +44,20 @@ type CertificateAuthority struct {
// And in particular: https://github.com/sigstore/protobuf-specs/pull/5/files#diff-b1f89b7fd3eb27b519380b092a2416f893a96fbba3f8c90cfa767e7687383ad4R70
// Well, not the multi-root, but one instance of that is exactly the
// SigstoreKeys.
type SigstoreKeys struct {
// Trusted certificate authorities (e.g Fulcio).
CertificateAuthorities []CertificateAuthority `json:"certificateAuthorities,omitempty"`
// Rekor log specifications
TLogs []TransparencyLogInstance `json:"tLogs,omitempty"`
// Certificate Transparency Log
CTLogs []TransparencyLogInstance `json:"ctLogs,omitempty"`
// Trusted timestamping authorities
TimeStampAuthorities []CertificateAuthority `json:"timestampAuthorities"`
}
type SigstoreKeys pbtrustroot.TrustedRoot

type CertificateAuthority = pbtrustroot.CertificateAuthority
type TransparencyLogInstance = pbtrustroot.TransparencyLogInstance
type DistinguishedName = pbcommon.DistinguishedName
type LogId = pbcommon.LogId

Check warning on line 52 in pkg/apis/config/sigstore_keys.go

View workflow job for this annotation

GitHub Actions / lint

var-naming: type LogId should be LogID (revive)

Check failure on line 52 in pkg/apis/config/sigstore_keys.go

View workflow job for this annotation

GitHub Actions / lint

ST1003: type LogId should be LogID (stylecheck)

type SigstoreKeysMap struct {
SigstoreKeys map[string]SigstoreKeys
SigstoreKeys map[string]*SigstoreKeys
}

// NewSigstoreKeysFromMap creates a map of SigstoreKeys to use for validation.
func NewSigstoreKeysFromMap(data map[string]string) (*SigstoreKeysMap, error) {
ret := make(map[string]SigstoreKeys, len(data))
ret := make(map[string]*SigstoreKeys, len(data))
// Spin through the ConfigMap. Each entry will have a serialized form of
// necessary validation keys in the form of SigstoreKeys.
for k, v := range data {
Expand All @@ -123,7 +73,7 @@ func NewSigstoreKeysFromMap(data map[string]string) (*SigstoreKeysMap, error) {
if err := parseSigstoreKeys(v, sigstoreKeys); err != nil {
return nil, fmt.Errorf("failed to parse the entry %q : %q : %w", k, v, err)
}
ret[k] = *sigstoreKeys
ret[k] = sigstoreKeys
}
return &SigstoreKeysMap{SigstoreKeys: ret}, nil
}
Expand All @@ -144,45 +94,87 @@ func parseSigstoreKeys(entry string, out interface{}) error {
// ConvertFrom takes a source and converts into a SigstoreKeys suitable
// for serialization into a ConfigMap entry.
func (sk *SigstoreKeys) ConvertFrom(_ context.Context, source *v1alpha1.SigstoreKeys) {
sk.CertificateAuthorities = make([]CertificateAuthority, len(source.CertificateAuthorities))
sk.CertificateAuthorities = make([]*pbtrustroot.CertificateAuthority, len(source.CertificateAuthorities))
for i := range source.CertificateAuthorities {
sk.CertificateAuthorities[i] = ConvertCertificateAuthority(source.CertificateAuthorities[i])
}

sk.TLogs = make([]TransparencyLogInstance, len(source.TLogs))
for i := range source.TLogs {
sk.TLogs[i] = ConvertTransparencyLogInstance(source.TLogs[i])
sk.Tlogs = make([]*pbtrustroot.TransparencyLogInstance, len(source.Tlogs))
for i := range source.Tlogs {
sk.Tlogs[i] = ConvertTransparencyLogInstance(source.Tlogs[i])
}

sk.CTLogs = make([]TransparencyLogInstance, len(source.CTLogs))
for i := range source.CTLogs {
sk.CTLogs[i] = ConvertTransparencyLogInstance(source.CTLogs[i])
sk.Ctlogs = make([]*pbtrustroot.TransparencyLogInstance, len(source.Ctlogs))
for i := range source.Ctlogs {
sk.Ctlogs[i] = ConvertTransparencyLogInstance(source.Ctlogs[i])
}

sk.TimeStampAuthorities = make([]CertificateAuthority, len(source.TimeStampAuthorities))
for i := range source.TimeStampAuthorities {
sk.TimeStampAuthorities[i] = ConvertCertificateAuthority(source.TimeStampAuthorities[i])
sk.TimestampAuthorities = make([]*pbtrustroot.CertificateAuthority, len(source.TimestampAuthorities))
for i := range source.TimestampAuthorities {
sk.TimestampAuthorities[i] = ConvertCertificateAuthority(source.TimestampAuthorities[i])
}
}

// ConvertCertificateAuthority converts public into private CertificateAuthority
func ConvertCertificateAuthority(source v1alpha1.CertificateAuthority) CertificateAuthority {
return CertificateAuthority{
Subject: DistinguishedName{
func ConvertCertificateAuthority(source v1alpha1.CertificateAuthority) *pbtrustroot.CertificateAuthority {

Check failure on line 119 in pkg/apis/config/sigstore_keys.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary leading newline (whitespace)

return &pbtrustroot.CertificateAuthority{
Subject: &pbcommon.DistinguishedName{
Organization: source.Subject.Organization,
CommonName: source.Subject.CommonName,
},
URI: *source.URI.DeepCopy(),
CertChain: source.CertChain,
Uri: source.URI.String(),
// TODO: convert certchain to *pbcommon.X509CertificateChain
CertChain: nil,
}
}

// ConvertTransparencyLogInstance converts public into private
// TransparencyLogInstance.
func ConvertTransparencyLogInstance(source v1alpha1.TransparencyLogInstance) TransparencyLogInstance {
return TransparencyLogInstance{
BaseURL: *source.BaseURL.DeepCopy(),
HashAlgorithm: source.HashAlgorithm,
PublicKey: source.PublicKey,
func ConvertTransparencyLogInstance(source v1alpha1.TransparencyLogInstance) *pbtrustroot.TransparencyLogInstance {
return &pbtrustroot.TransparencyLogInstance{
BaseUrl: source.BaseUrl.String(),
HashAlgorithm: 0, // TODO: convert source.HashAlgorithm to pbcommon.HashAlgorithm
PublicKey: nil, // TODO: convert source.PublicKey to *pbcommon.PublicKey
// TODO: LogId
}
}

func SerializeCertChain(certChain *pbcommon.X509CertificateChain) []byte {
var chain []byte
for _, cert := range certChain.Certificates {
bytes := cert.RawBytes
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: bytes,
}
chain = append(chain, pem.EncodeToMemory(block)...)
}
return chain
}

func SerializePublicKey(publicKey *pbcommon.PublicKey) []byte {
block := &pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKey.RawBytes,
}
return pem.EncodeToMemory(block)
}

func DeserializeCertChain(chain []byte) *pbcommon.X509CertificateChain {
var certs []*pbcommon.X509Certificate
for {
var block *pem.Block
block, chain = pem.Decode(chain)
if block == nil {
break
}
certs = append(certs, &pbcommon.X509Certificate{RawBytes: block.Bytes})
}
return &pbcommon.X509CertificateChain{Certificates: certs}
}

func DeserializePublicKey(publicKey []byte) *pbcommon.PublicKey {
block, _ := pem.Decode(publicKey)
return &pbcommon.PublicKey{RawBytes: block.Bytes}
}
4 changes: 2 additions & 2 deletions pkg/apis/config/sigstore_keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ func TestDefaultsSigstoreKeysConfigurationFromFile(t *testing.T) {
// Note that even though sigstoreKeys.TLog[0].PublicKey is base64 encoded
// in the ConfigMap it gets decoded when we fetch it above, so we get the
// PEM format for it directly. Same for tsaCertChain
got = string(sigstoreKeys.TLogs[0].PublicKey)
got = string(sigstoreKeys.Tlogs[0].PublicKey.RawBytes)
if got != rekorPublicKey {
t.Errorf("Invalid public key, want %s got %s", rekorPublicKey, got)
}
got = string(sigstoreKeys.TimeStampAuthorities[0].CertChain)
got = string(SerializeCertChain(sigstoreKeys.TimestampAuthorities[0].CertChain))
if got != tsaCertChain {
t.Errorf("Invalid cert chain, want %s got %s", tsaCertChain, got)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/policy/v1alpha1/clusterimagepolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ type Identity struct {
// RFC3161Timestamp specifies the URL to a RFC3161 time-stamping server that holds
// the time-stamped verification for the signature
type RFC3161Timestamp struct {
// Use the Certificate Chain from the referred TrustRoot.TimeStampAuthorities
// Use the Certificate Chain from the referred TrustRoot.TimestampAuthorities
// +optional
TrustRootRef string `json:"trustRootRef,omitempty"`
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/apis/policy/v1alpha1/trustroot_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ type Repository struct {
// and verify an inclusion promise.
type TransparencyLogInstance struct {
// The base URL which can be used for URLs for clients.
BaseURL apis.URL `json:"baseURL"`
BaseUrl apis.URL `json:"baseURL"`

Check warning on line 139 in pkg/apis/policy/v1alpha1/trustroot_types.go

View workflow job for this annotation

GitHub Actions / lint

var-naming: struct field BaseUrl should be BaseURL (revive)

Check failure on line 139 in pkg/apis/policy/v1alpha1/trustroot_types.go

View workflow job for this annotation

GitHub Actions / lint

ST1003: struct field BaseUrl should be BaseURL (stylecheck)
// / The hash algorithm used for the Merkle Tree
HashAlgorithm string `json:"hashAlgorithm"`
// PEM encoded public key
Expand Down Expand Up @@ -180,13 +180,13 @@ type SigstoreKeys struct {
CertificateAuthorities []CertificateAuthority `json:"certificateAuthorities"`
// Rekor log specifications
// +optional
TLogs []TransparencyLogInstance `json:"tLogs,omitempty"`
Tlogs []TransparencyLogInstance `json:"tLogs,omitempty"`
// Certificate Transparency Log
// +optional
CTLogs []TransparencyLogInstance `json:"ctLogs,omitempty"`
Ctlogs []TransparencyLogInstance `json:"ctLogs,omitempty"`
// Trusted timestamping authorities
// +optional
TimeStampAuthorities []CertificateAuthority `json:"timestampAuthorities,omitempty"`
TimestampAuthorities []CertificateAuthority `json:"timestampAuthorities,omitempty"`
}

// TrustRootStatus represents the current state of a TrustRoot.
Expand Down
10 changes: 5 additions & 5 deletions pkg/apis/policy/v1alpha1/trustroot_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (remote *Remote) Validate(ctx context.Context) (errors *apis.FieldError) {
}

func (sigstoreKeys *SigstoreKeys) Validate(ctx context.Context) (errors *apis.FieldError) {
if len(sigstoreKeys.CertificateAuthorities) == 0 && len(sigstoreKeys.TimeStampAuthorities) == 0 {
if len(sigstoreKeys.CertificateAuthorities) == 0 && len(sigstoreKeys.TimestampAuthorities) == 0 {
errors = errors.Also(apis.ErrMissingOneOf("certificateAuthority", "timestampAuthorities"))
} else {
for i, ca := range sigstoreKeys.CertificateAuthorities {
Expand All @@ -116,13 +116,13 @@ func (sigstoreKeys *SigstoreKeys) Validate(ctx context.Context) (errors *apis.Fi

// These are optionals, so we just validate them if they are there and do
// not report them as missing.
for i, tsa := range sigstoreKeys.TimeStampAuthorities {
for i, tsa := range sigstoreKeys.TimestampAuthorities {
errors = ValidateTimeStampAuthority(ctx, tsa).ViaFieldIndex("timestampAuthorities", i)
}
for i, ctl := range sigstoreKeys.CTLogs {
for i, ctl := range sigstoreKeys.Ctlogs {
errors = ValidateTransparencyLogInstance(ctx, ctl).ViaFieldIndex("ctLogs", i)
}
for i, tl := range sigstoreKeys.TLogs {
for i, tl := range sigstoreKeys.Tlogs {
errors = ValidateTransparencyLogInstance(ctx, tl).ViaFieldIndex("tLogs", i)
}
return
Expand Down Expand Up @@ -183,7 +183,7 @@ func ValidateDistinguishedName(_ context.Context, dn DistinguishedName) (errors
}

func ValidateTransparencyLogInstance(_ context.Context, tli TransparencyLogInstance) (errors *apis.FieldError) {
if tli.BaseURL.String() == "" {
if tli.BaseUrl.String() == "" {
errors = errors.Also(apis.ErrMissingField("baseURL"))
}
if tli.HashAlgorithm == "" {
Expand Down
14 changes: 7 additions & 7 deletions pkg/apis/policy/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/apis/policy/v1beta1/clusterimagepolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ type Identity struct {
// RFC3161Timestamp specifies the URL to a RFC3161 time-stamping server that holds
// the time-stamped verification for the signature
type RFC3161Timestamp struct {
// Use the Certificate Chain from the referred TrustRoot.TimeStampAuthorities
// Use the Certificate Chain from the referred TrustRoot.TimestampAuthorities
// +optional
TrustRootRef string `json:"trustRootRef,omitempty"`
}
Expand Down
Loading

0 comments on commit 0975b9e

Please sign in to comment.