mirror of
https://github.com/containers/skopeo.git
synced 2025-09-12 13:08:02 +00:00
Add policy configuration data structures, construction and parsing
This commit is contained in:
84
signature/fixtures/policy.json
Normal file
84
signature/fixtures/policy.json
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"default": [
|
||||||
|
{
|
||||||
|
"type": "reject"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"specific": {
|
||||||
|
"example.com/playground": [
|
||||||
|
{
|
||||||
|
"type": "insecureAcceptAnything"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example.com/production": [
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "GPGKeys",
|
||||||
|
"keyPath": "/keys/employee-gpg-keyring"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example.com/hardened": [
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "GPGKeys",
|
||||||
|
"keyPath": "/keys/employee-gpg-keyring",
|
||||||
|
"signedIdentity": {
|
||||||
|
"type": "matchRepository"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "signedByGPGKeys",
|
||||||
|
"keyPath": "/keys/public-key-signing-gpg-keyring",
|
||||||
|
"signedIdentity": {
|
||||||
|
"type": "matchExact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "signedBaseLayer",
|
||||||
|
"baseLayerIdentity": {
|
||||||
|
"type": "exactRepository",
|
||||||
|
"dockerRepository": "registry.access.redhat.com/rhel7/rhel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example.com/hardened-x509": [
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "X509Certificates",
|
||||||
|
"keyPath": "/keys/employee-cert-file",
|
||||||
|
"signedIdentity": {
|
||||||
|
"type": "matchRepository"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "signedByX509CAs",
|
||||||
|
"keyPath": "/keys/public-key-signing-ca-file"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"registry.access.redhat.com": [
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "signedByGPGKeys",
|
||||||
|
"keyPath": "/keys/RH-key-signing-key-gpg-keyring"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bogus/key-data-example": [
|
||||||
|
{
|
||||||
|
"type": "signedBy",
|
||||||
|
"keyType": "signedByGPGKeys",
|
||||||
|
"keyData": "bm9uc2Vuc2U="
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bogus/signed-identity-example": [
|
||||||
|
{
|
||||||
|
"type": "signedBaseLayer",
|
||||||
|
"baseLayerIdentity": {
|
||||||
|
"type": "exactReference",
|
||||||
|
"dockerReference": "registry.access.redhat.com/rhel7/rhel:latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
612
signature/policy_config.go
Normal file
612
signature/policy_config.go
Normal file
@@ -0,0 +1,612 @@
|
|||||||
|
// policy_config.go hanles creation of policy objects, either by parsing JSON
|
||||||
|
// or by programs building them programmatically.
|
||||||
|
|
||||||
|
// The New* constructors are intended to be a stable API. FIXME: after an independent review.
|
||||||
|
|
||||||
|
// Do not invoke the internals of the JSON marshaling/unmarshaling directly.
|
||||||
|
|
||||||
|
// We can't just blindly call json.Unmarshal because that would silently ignore
|
||||||
|
// typos, and that would just not do for security policy.
|
||||||
|
|
||||||
|
// FIXME? This is by no means an user-friendly parser: No location information in error messages, no other context.
|
||||||
|
// But at least it is not worse than blind json.Unmarshal()…
|
||||||
|
|
||||||
|
package signature
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/projectatomic/skopeo/reference"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InvalidPolicyFormatError is returned when parsing an invalid policy configuration.
|
||||||
|
type InvalidPolicyFormatError string
|
||||||
|
|
||||||
|
func (err InvalidPolicyFormatError) Error() string {
|
||||||
|
return string(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: NewDefaultPolicy, from default file (or environment if trusted?)
|
||||||
|
|
||||||
|
// NewPolicyFromFile returns a policy configured in the specified file.
|
||||||
|
func NewPolicyFromFile(fileName string) (*Policy, error) {
|
||||||
|
contents, err := ioutil.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewPolicyFromBytes(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPolicyFromBytes returns a policy parsed from the specified blob.
|
||||||
|
// Use this function instead of calling json.Unmarshal directly.
|
||||||
|
func NewPolicyFromBytes(data []byte) (*Policy, error) {
|
||||||
|
p := Policy{}
|
||||||
|
if err := json.Unmarshal(data, &p); err != nil {
|
||||||
|
return nil, InvalidPolicyFormatError(err.Error())
|
||||||
|
}
|
||||||
|
return &p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that Policy implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*Policy)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (p *Policy) UnmarshalJSON(data []byte) error {
|
||||||
|
*p = Policy{}
|
||||||
|
specific := policySpecificMap{}
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
switch key {
|
||||||
|
case "default":
|
||||||
|
return &p.Default
|
||||||
|
case "specific":
|
||||||
|
return &specific
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Default == nil {
|
||||||
|
return InvalidPolicyFormatError("Default policy is missing")
|
||||||
|
}
|
||||||
|
p.Specific = map[string]PolicyRequirements(specific)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// policySpecificMap is a specialization of this map type for the strict JSON parsing semantics appropriate for the Policy.Specific member.
|
||||||
|
type policySpecificMap map[string]PolicyRequirements
|
||||||
|
|
||||||
|
// Compile-time check that policySpecificMap implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*policySpecificMap)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (m *policySpecificMap) UnmarshalJSON(data []byte) error {
|
||||||
|
// We can't unmarshal directly into map values because it is not possible to take an address of a map value.
|
||||||
|
// So, use a temporary map of pointers-to-slices and convert.
|
||||||
|
tmpMap := map[string]*PolicyRequirements{}
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
// Check that the scope format is at least plausible.
|
||||||
|
if _, err := reference.ParseNamed(key); err != nil {
|
||||||
|
return nil // FIXME? This returns an "Unknown key" error instead of saying that the format is invalid.
|
||||||
|
}
|
||||||
|
// paranoidUnmarshalJSONObject detects key duplication for us, check just to be safe.
|
||||||
|
if _, ok := tmpMap[key]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ptr := &PolicyRequirements{} // This allocates a new instance on each call.
|
||||||
|
tmpMap[key] = ptr
|
||||||
|
return ptr
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for key, ptr := range tmpMap {
|
||||||
|
(*m)[key] = *ptr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that PolicyRequirements implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*PolicyRequirements)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (m *PolicyRequirements) UnmarshalJSON(data []byte) error {
|
||||||
|
reqJSONs := []json.RawMessage{}
|
||||||
|
if err := json.Unmarshal(data, &reqJSONs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(reqJSONs) == 0 {
|
||||||
|
return InvalidPolicyFormatError("List of verification policy requirements must not be empty")
|
||||||
|
}
|
||||||
|
res := make([]PolicyRequirement, len(reqJSONs))
|
||||||
|
for i, reqJSON := range reqJSONs {
|
||||||
|
req, err := newPolicyRequirementFromJSON(reqJSON)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res[i] = req
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPolicyRequirementFromJSON parses JSON data into a PolicyRequirement implementation.
|
||||||
|
func newPolicyRequirementFromJSON(data []byte) (PolicyRequirement, error) {
|
||||||
|
var typeField prCommon
|
||||||
|
if err := json.Unmarshal(data, &typeField); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res PolicyRequirement
|
||||||
|
switch typeField.Type {
|
||||||
|
case prTypeInsecureAcceptAnything:
|
||||||
|
res = &prInsecureAcceptAnything{}
|
||||||
|
case prTypeReject:
|
||||||
|
res = &prReject{}
|
||||||
|
case prTypeSignedBy:
|
||||||
|
res = &prSignedBy{}
|
||||||
|
case prTypeSignedBaseLayer:
|
||||||
|
res = &prSignedBaseLayer{}
|
||||||
|
default:
|
||||||
|
return nil, InvalidPolicyFormatError(fmt.Sprintf("Unknown policy requirement type \"%s\"", typeField.Type))
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &res); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRInsecureAcceptAnything is NewPRInsecureAcceptAnything, except it returns the private type.
|
||||||
|
func newPRInsecureAcceptAnything() *prInsecureAcceptAnything {
|
||||||
|
return &prInsecureAcceptAnything{prCommon{Type: prTypeInsecureAcceptAnything}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPRInsecureAcceptAnything returns a new "insecureAcceptAnything" PolicyRequirement.
|
||||||
|
func NewPRInsecureAcceptAnything() PolicyRequirement {
|
||||||
|
return newPRInsecureAcceptAnything()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that prInsecureAcceptAnything implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*prInsecureAcceptAnything)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (pr *prInsecureAcceptAnything) UnmarshalJSON(data []byte) error {
|
||||||
|
*pr = prInsecureAcceptAnything{}
|
||||||
|
var tmp prInsecureAcceptAnything
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
switch key {
|
||||||
|
case "type":
|
||||||
|
return &tmp.Type
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Type != prTypeInsecureAcceptAnything {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||||
|
}
|
||||||
|
*pr = *newPRInsecureAcceptAnything()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRReject is NewPRReject, except it returns the private type.
|
||||||
|
func newPRReject() *prReject {
|
||||||
|
return &prReject{prCommon{Type: prTypeReject}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPRReject returns a new "reject" PolicyRequirement.
|
||||||
|
func NewPRReject() PolicyRequirement {
|
||||||
|
return newPRReject()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that prReject implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*prReject)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (pr *prReject) UnmarshalJSON(data []byte) error {
|
||||||
|
*pr = prReject{}
|
||||||
|
var tmp prReject
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
switch key {
|
||||||
|
case "type":
|
||||||
|
return &tmp.Type
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Type != prTypeReject {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||||
|
}
|
||||||
|
*pr = *newPRReject()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRSignedBy returns a new prSignedBy if parameters are valid.
|
||||||
|
func newPRSignedBy(keyType sbKeyType, keyPath string, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||||
|
if !keyType.IsValid() {
|
||||||
|
return nil, InvalidPolicyFormatError(fmt.Sprintf("invalid keyType \"%s\"", keyType))
|
||||||
|
}
|
||||||
|
if len(keyPath) > 0 && len(keyData) > 0 {
|
||||||
|
return nil, InvalidPolicyFormatError("keyType and keyData cannot be used simultaneously")
|
||||||
|
}
|
||||||
|
if signedIdentity == nil {
|
||||||
|
return nil, InvalidPolicyFormatError("signedIdentity not specified")
|
||||||
|
}
|
||||||
|
return &prSignedBy{
|
||||||
|
prCommon: prCommon{Type: prTypeSignedBy},
|
||||||
|
KeyType: keyType,
|
||||||
|
KeyPath: keyPath,
|
||||||
|
KeyData: keyData,
|
||||||
|
SignedIdentity: signedIdentity,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRSignedByKeyPath is NewPRSignedByKeyPath, except it returns the private type.
|
||||||
|
func newPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||||
|
return newPRSignedBy(keyType, keyPath, nil, signedIdentity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPRSignedByKeyPath returns a new "signedBy" PolicyRequirement using a KeyPath
|
||||||
|
func NewPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) (PolicyRequirement, error) {
|
||||||
|
return newPRSignedByKeyPath(keyType, keyPath, signedIdentity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRSignedByKeyData is NewPRSignedByKeyData, except it returns the private type.
|
||||||
|
func newPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) {
|
||||||
|
return newPRSignedBy(keyType, "", keyData, signedIdentity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPRSignedByKeyData returns a new "signedBy" PolicyRequirement using a KeyData
|
||||||
|
func NewPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch) (PolicyRequirement, error) {
|
||||||
|
return newPRSignedByKeyData(keyType, keyData, signedIdentity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that prSignedBy implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*prSignedBy)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (pr *prSignedBy) UnmarshalJSON(data []byte) error {
|
||||||
|
*pr = prSignedBy{}
|
||||||
|
var tmp prSignedBy
|
||||||
|
var gotKeyPath, gotKeyData = false, false
|
||||||
|
var signedIdentity json.RawMessage
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
switch key {
|
||||||
|
case "type":
|
||||||
|
return &tmp.Type
|
||||||
|
case "keyType":
|
||||||
|
return &tmp.KeyType
|
||||||
|
case "keyPath":
|
||||||
|
gotKeyPath = true
|
||||||
|
return &tmp.KeyPath
|
||||||
|
case "keyData":
|
||||||
|
gotKeyData = true
|
||||||
|
return &tmp.KeyData
|
||||||
|
case "signedIdentity":
|
||||||
|
return &signedIdentity
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Type != prTypeSignedBy {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||||
|
}
|
||||||
|
if signedIdentity == nil {
|
||||||
|
tmp.SignedIdentity = NewPRMMatchExact()
|
||||||
|
} else {
|
||||||
|
si, err := newPolicyReferenceMatchFromJSON(signedIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tmp.SignedIdentity = si
|
||||||
|
}
|
||||||
|
|
||||||
|
var res *prSignedBy
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case gotKeyPath && gotKeyData:
|
||||||
|
return InvalidPolicyFormatError("keyPath and keyData cannot be used simultaneously")
|
||||||
|
case gotKeyPath && !gotKeyData:
|
||||||
|
res, err = newPRSignedByKeyPath(tmp.KeyType, tmp.KeyPath, tmp.SignedIdentity)
|
||||||
|
case !gotKeyPath && gotKeyData:
|
||||||
|
res, err = newPRSignedByKeyData(tmp.KeyType, tmp.KeyData, tmp.SignedIdentity)
|
||||||
|
case !gotKeyPath && !gotKeyData:
|
||||||
|
return InvalidPolicyFormatError("At least one of keyPath and keyData mus be specified")
|
||||||
|
default: // Coverage: This should never happen
|
||||||
|
return fmt.Errorf("Impossible keyPath/keyData presence combination!?")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*pr = *res
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid returns true iff kt is a recognized value
|
||||||
|
func (kt sbKeyType) IsValid() bool {
|
||||||
|
switch kt {
|
||||||
|
case SBKeyTypeGPGKeys, SBKeyTypeSignedByGPGKeys,
|
||||||
|
SBKeyTypeX509Certificates, SBKeyTypeSignedByX509CAs:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that sbKeyType implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*sbKeyType)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (kt *sbKeyType) UnmarshalJSON(data []byte) error {
|
||||||
|
*kt = sbKeyType("")
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !sbKeyType(s).IsValid() {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("Unrecognized keyType value \"%s\"", s))
|
||||||
|
}
|
||||||
|
*kt = sbKeyType(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRSignedBaseLayer is NewPRSignedBaseLayer, except it returns the private type.
|
||||||
|
func newPRSignedBaseLayer(baseLayerIdentity PolicyReferenceMatch) (*prSignedBaseLayer, error) {
|
||||||
|
if baseLayerIdentity == nil {
|
||||||
|
return nil, InvalidPolicyFormatError("baseLayerIdenitty not specified")
|
||||||
|
}
|
||||||
|
return &prSignedBaseLayer{
|
||||||
|
prCommon: prCommon{Type: prTypeSignedBaseLayer},
|
||||||
|
BaseLayerIdentity: baseLayerIdentity,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPRSignedBaseLayer returns a new "signedBaseLayer" PolicyRequirement.
|
||||||
|
func NewPRSignedBaseLayer(baseLayerIdentity PolicyReferenceMatch) (PolicyRequirement, error) {
|
||||||
|
return newPRSignedBaseLayer(baseLayerIdentity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that prSignedBaseLayer implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*prSignedBaseLayer)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (pr *prSignedBaseLayer) UnmarshalJSON(data []byte) error {
|
||||||
|
*pr = prSignedBaseLayer{}
|
||||||
|
var tmp prSignedBaseLayer
|
||||||
|
var baseLayerIdentity json.RawMessage
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
switch key {
|
||||||
|
case "type":
|
||||||
|
return &tmp.Type
|
||||||
|
case "baseLayerIdentity":
|
||||||
|
return &baseLayerIdentity
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Type != prTypeSignedBaseLayer {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||||
|
}
|
||||||
|
if baseLayerIdentity == nil {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("baseLayerIdentity not specified"))
|
||||||
|
}
|
||||||
|
bli, err := newPolicyReferenceMatchFromJSON(baseLayerIdentity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res, err := newPRSignedBaseLayer(bli)
|
||||||
|
if err != nil {
|
||||||
|
// Coverage: This should never happen, newPolicyReferenceMatchFromJSON has ensured bli is valid.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*pr = *res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPolicyRequirementFromJSON parses JSON data into a PolicyReferenceMatch implementation.
|
||||||
|
func newPolicyReferenceMatchFromJSON(data []byte) (PolicyReferenceMatch, error) {
|
||||||
|
var typeField prmCommon
|
||||||
|
if err := json.Unmarshal(data, &typeField); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res PolicyReferenceMatch
|
||||||
|
switch typeField.Type {
|
||||||
|
case prmTypeMatchExact:
|
||||||
|
res = &prmMatchExact{}
|
||||||
|
case prmTypeMatchRepository:
|
||||||
|
res = &prmMatchRepository{}
|
||||||
|
case prmTypeExactReference:
|
||||||
|
res = &prmExactReference{}
|
||||||
|
case prmTypeExactRepository:
|
||||||
|
res = &prmExactRepository{}
|
||||||
|
default:
|
||||||
|
return nil, InvalidPolicyFormatError(fmt.Sprintf("Unknown policy reference match type \"%s\"", typeField.Type))
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &res); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRMMatchExact is NewPRMMatchExact, except it resturns the private type.
|
||||||
|
func newPRMMatchExact() *prmMatchExact {
|
||||||
|
return &prmMatchExact{prmCommon{Type: prmTypeMatchExact}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPRMMatchExact returns a new "matchExact" PolicyReferenceMatch.
|
||||||
|
func NewPRMMatchExact() PolicyReferenceMatch {
|
||||||
|
return newPRMMatchExact()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that prmMatchExact implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*prmMatchExact)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (prm *prmMatchExact) UnmarshalJSON(data []byte) error {
|
||||||
|
*prm = prmMatchExact{}
|
||||||
|
var tmp prmMatchExact
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
switch key {
|
||||||
|
case "type":
|
||||||
|
return &tmp.Type
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Type != prmTypeMatchExact {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||||
|
}
|
||||||
|
*prm = *newPRMMatchExact()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRMMatchRepository is NewPRMMatchRepository, except it resturns the private type.
|
||||||
|
func newPRMMatchRepository() *prmMatchRepository {
|
||||||
|
return &prmMatchRepository{prmCommon{Type: prmTypeMatchRepository}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPRMMatchRepository returns a new "matchRepository" PolicyReferenceMatch.
|
||||||
|
func NewPRMMatchRepository() PolicyReferenceMatch {
|
||||||
|
return newPRMMatchRepository()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that prmMatchRepository implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*prmMatchRepository)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (prm *prmMatchRepository) UnmarshalJSON(data []byte) error {
|
||||||
|
*prm = prmMatchRepository{}
|
||||||
|
var tmp prmMatchRepository
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
switch key {
|
||||||
|
case "type":
|
||||||
|
return &tmp.Type
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Type != prmTypeMatchRepository {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||||
|
}
|
||||||
|
*prm = *newPRMMatchRepository()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRMExactReference is NewPRMExactReference, except it resturns the private type.
|
||||||
|
func newPRMExactReference(dockerReference string) (*prmExactReference, error) {
|
||||||
|
ref, err := reference.ParseNamed(dockerReference)
|
||||||
|
if err != nil {
|
||||||
|
return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerReference %s: %s", dockerReference, err.Error()))
|
||||||
|
}
|
||||||
|
if reference.IsNameOnly(ref) {
|
||||||
|
return nil, InvalidPolicyFormatError(fmt.Sprintf("dockerReference %s contains neither a tag nor digest", dockerReference))
|
||||||
|
}
|
||||||
|
return &prmExactReference{
|
||||||
|
prmCommon: prmCommon{Type: prmTypeExactReference},
|
||||||
|
DockerReference: dockerReference,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPRMExactReference returns a new "exactReference" PolicyReferenceMatch.
|
||||||
|
func NewPRMExactReference(dockerReference string) (PolicyReferenceMatch, error) {
|
||||||
|
return newPRMExactReference(dockerReference)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that prmExactReference implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*prmExactReference)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (prm *prmExactReference) UnmarshalJSON(data []byte) error {
|
||||||
|
*prm = prmExactReference{}
|
||||||
|
var tmp prmExactReference
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
switch key {
|
||||||
|
case "type":
|
||||||
|
return &tmp.Type
|
||||||
|
case "dockerReference":
|
||||||
|
return &tmp.DockerReference
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Type != prmTypeExactReference {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := newPRMExactReference(tmp.DockerReference)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*prm = *res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPRMExactRepository is NewPRMExactRepository, except it resturns the private type.
|
||||||
|
func newPRMExactRepository(dockerRepository string) (*prmExactRepository, error) {
|
||||||
|
if _, err := reference.ParseNamed(dockerRepository); err != nil {
|
||||||
|
return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerRepository %s: %s", dockerRepository, err.Error()))
|
||||||
|
}
|
||||||
|
return &prmExactRepository{
|
||||||
|
prmCommon: prmCommon{Type: prmTypeExactRepository},
|
||||||
|
DockerRepository: dockerRepository,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPRMExactRepository returns a new "exactRepository" PolicyRepositoryMatch.
|
||||||
|
func NewPRMExactRepository(dockerRepository string) (PolicyReferenceMatch, error) {
|
||||||
|
return newPRMExactRepository(dockerRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time check that prmExactRepository implements json.Unmarshaler.
|
||||||
|
var _ json.Unmarshaler = (*prmExactRepository)(nil)
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (prm *prmExactRepository) UnmarshalJSON(data []byte) error {
|
||||||
|
*prm = prmExactRepository{}
|
||||||
|
var tmp prmExactRepository
|
||||||
|
if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} {
|
||||||
|
switch key {
|
||||||
|
case "type":
|
||||||
|
return &tmp.Type
|
||||||
|
case "dockerRepository":
|
||||||
|
return &tmp.DockerRepository
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Type != prmTypeExactRepository {
|
||||||
|
return InvalidPolicyFormatError(fmt.Sprintf("Unexpected policy requirement type \"%s\"", tmp.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := newPRMExactRepository(tmp.DockerRepository)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*prm = *res
|
||||||
|
return nil
|
||||||
|
}
|
1177
signature/policy_config_test.go
Normal file
1177
signature/policy_config_test.go
Normal file
File diff suppressed because it is too large
Load Diff
136
signature/policy_types.go
Normal file
136
signature/policy_types.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// Note: Consider the API unstable until the code supports at least three different image formats or transports.
|
||||||
|
|
||||||
|
// This defines types used to represent a signature verification policy in memory.
|
||||||
|
// Do not use the private types directly; either parse a configuration file, or construct a Policy from PolicyRequirements
|
||||||
|
// built using the constructor functions provided in policy_config.go.
|
||||||
|
|
||||||
|
package signature
|
||||||
|
|
||||||
|
// Policy defines requirements for considering a signature valid.
|
||||||
|
type Policy struct {
|
||||||
|
// Default applies to any image which does not have a matching policy in Specific.
|
||||||
|
Default PolicyRequirements `json:"default"`
|
||||||
|
// Specific applies to images matching scope, the map key.
|
||||||
|
// Scope is registry/server, namespace in a registry, single repository.
|
||||||
|
// FIXME: Scope syntax - should it be namespaced docker:something ? Or, in the worst case, a composite object (we couldn't use a JSON map)
|
||||||
|
// Most specific scope wins, duplication is prohibited (hard failure).
|
||||||
|
// Defaults to an empty map if not specified.
|
||||||
|
Specific map[string]PolicyRequirements `json:"specific"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PolicyRequirements is a set of requirements applying to a set of images; each of them must be satisfied (though perhaps each by a different signature).
|
||||||
|
// Must not be empty, frequently will only contain a single element.
|
||||||
|
type PolicyRequirements []PolicyRequirement
|
||||||
|
|
||||||
|
// PolicyRequirement is a rule which must be satisfied by at least one of the signatures of an image.
|
||||||
|
// The type is public, but its definition is private.
|
||||||
|
type PolicyRequirement interface{} // Will be expanded and moved elsewhere later.
|
||||||
|
|
||||||
|
// prCommon is the common type field in a JSON encoding of PolicyRequirement.
|
||||||
|
type prCommon struct {
|
||||||
|
Type prTypeIdentifier `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// prTypeIdentifier is string designating a kind of a PolicyRequirement.
|
||||||
|
type prTypeIdentifier string
|
||||||
|
|
||||||
|
const (
|
||||||
|
prTypeInsecureAcceptAnything prTypeIdentifier = "insecureAcceptAnything"
|
||||||
|
prTypeReject prTypeIdentifier = "reject"
|
||||||
|
prTypeSignedBy prTypeIdentifier = "signedBy"
|
||||||
|
prTypeSignedBaseLayer prTypeIdentifier = "signedBaseLayer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// prInsecureAcceptAnything is a PolicyRequirement with type = prTypeInsecureAcceptAnything: every image is accepted.
|
||||||
|
// Note that because PolicyRequirements are implicitly ANDed, this is necessary only if it is the only rule (to make the list non-empty and the policy explicit).
|
||||||
|
type prInsecureAcceptAnything struct {
|
||||||
|
prCommon
|
||||||
|
}
|
||||||
|
|
||||||
|
// prReject is a PolicyRequirement with type = prTypeReject: every image is rejected.
|
||||||
|
type prReject struct {
|
||||||
|
prCommon
|
||||||
|
}
|
||||||
|
|
||||||
|
// prSignedBy is a PolicyRequirement with type = prTypeSignedBy: the image is signed by trusted keys for a specified identity
|
||||||
|
type prSignedBy struct {
|
||||||
|
prCommon
|
||||||
|
|
||||||
|
// KeyType specifies what kind of key reference KeyPath/KeyData is.
|
||||||
|
// Acceptable values are “GPGKeys” | “signedByGPGKeys” “X.509Certificates” | “signedByX.509CAs”
|
||||||
|
// FIXME: eventually also support GPGTOFU, X.509TOFU, with KeyPath only
|
||||||
|
KeyType sbKeyType `json:"keyType"`
|
||||||
|
|
||||||
|
// KeyPath is a pathname to a local file containing the trusted key(s). Exactly one of KeyPath and KeyData must be specified.
|
||||||
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
|
// KeyData contains the trusted key(s), base64-encoded. Exactly one of KeyPath and KeyData must be specified.
|
||||||
|
KeyData []byte `json:"keyData,omitempty"`
|
||||||
|
|
||||||
|
// SignedIdentity specifies what image identity the signature must be claiming about the image.
|
||||||
|
// Defaults to "match-exact" if not specified.
|
||||||
|
SignedIdentity PolicyReferenceMatch `json:"signedIdentity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sbKeyType are the allowed values for prSignedBy.KeyType
|
||||||
|
type sbKeyType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SBKeyTypeGPGKeys refers to keys contained in a GPG keyring
|
||||||
|
SBKeyTypeGPGKeys sbKeyType = "GPGKeys"
|
||||||
|
// SBKeyTypeSignedByGPGKeys refers to keys signed by keys in a GPG keyring
|
||||||
|
SBKeyTypeSignedByGPGKeys sbKeyType = "signedByGPGKeys"
|
||||||
|
// SBKeyTypeX509Certificates refers to keys in a set of X.509 certificates
|
||||||
|
// FIXME: PEM, DER?
|
||||||
|
SBKeyTypeX509Certificates sbKeyType = "X509Certificates"
|
||||||
|
// SBKeyTypeSignedByX509CAs refers to keys signed by one of the X.509 CAs
|
||||||
|
// FIXME: PEM, DER?
|
||||||
|
SBKeyTypeSignedByX509CAs sbKeyType = "signedByX509CAs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// prSignedBaseLayer is a PolicyRequirement with type = prSignedBaseLayer: the image has a specified, correctly signed, base image.
|
||||||
|
type prSignedBaseLayer struct {
|
||||||
|
prCommon
|
||||||
|
// BaseLayerIdentity specifies the base image to look for. "match-exact" is rejected, "match-repository" is unlikely to be useful.
|
||||||
|
BaseLayerIdentity PolicyReferenceMatch `json:"baseLayerIdentity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PolicyReferenceMatch specifies a set of image identities accepted in PolicyRequirement.
|
||||||
|
// The type is public, but its implementation is private.
|
||||||
|
type PolicyReferenceMatch interface{} // Will be expanded and moved elsewhere later.
|
||||||
|
|
||||||
|
// prmCommon is the common type field in a JSON encoding of PolicyReferenceMatch.
|
||||||
|
type prmCommon struct {
|
||||||
|
Type prmTypeIdentifier `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// prmTypeIdentifier is string designating a kind of a PolicyReferenceMatch.
|
||||||
|
type prmTypeIdentifier string
|
||||||
|
|
||||||
|
const (
|
||||||
|
prmTypeMatchExact prmTypeIdentifier = "matchExact"
|
||||||
|
prmTypeMatchRepository prmTypeIdentifier = "matchRepository"
|
||||||
|
prmTypeExactReference prmTypeIdentifier = "exactReference"
|
||||||
|
prmTypeExactRepository prmTypeIdentifier = "exactRepository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// prmMatchExact is a PolicyReferenceMatch with type = prmMatchExact: the two references must match exactly.
|
||||||
|
type prmMatchExact struct {
|
||||||
|
prmCommon
|
||||||
|
}
|
||||||
|
|
||||||
|
// prmMatchRepository is a PolicyReferenceMatch with type = prmMatchRepository: the two references must use the same repository, may differ in the tag.
|
||||||
|
type prmMatchRepository struct {
|
||||||
|
prmCommon
|
||||||
|
}
|
||||||
|
|
||||||
|
// prmExactReference is a PolicyReferenceMatch with type = prmExactReference: matches a specified reference exactly.
|
||||||
|
type prmExactReference struct {
|
||||||
|
prmCommon
|
||||||
|
DockerReference string `json:"dockerReference"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// prmExactRepository is a PolicyReferenceMatch with type = prmExactRepository: matches a specified repository, with any tag.
|
||||||
|
type prmExactRepository struct {
|
||||||
|
prmCommon
|
||||||
|
DockerRepository string `json:"dockerRepository"`
|
||||||
|
}
|
Reference in New Issue
Block a user