diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index 3eb305144..db7d1e882 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -37,6 +37,7 @@ import ( "github.com/go-playground/validator" "github.com/sigstore/rekor/pkg/generated/models" alpine_v001 "github.com/sigstore/rekor/pkg/types/alpine/v0.0.1" + helm_v001 "github.com/sigstore/rekor/pkg/types/helm/v0.0.1" intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" jar_v001 "github.com/sigstore/rekor/pkg/types/jar/v0.0.1" rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1" @@ -149,6 +150,83 @@ func validateArtifactPFlags(uuidValid, indexValid bool) error { return nil } +func CreateHelmFromPFlags() (models.ProposedEntry, error) { + //TODO: how to select version of item to create + returnVal := models.Helm{} + re := new(helm_v001.V001Entry) + + helm := viper.GetString("entry") + if helm != "" { + var helmBytes []byte + provURL, err := url.Parse(helm) + if err == nil && provURL.IsAbs() { + /* #nosec G107 */ + helmResp, err := http.Get(helm) + if err != nil { + return nil, fmt.Errorf("error fetching 'helm': %w", err) + } + defer helmResp.Body.Close() + helmBytes, err = ioutil.ReadAll(helmResp.Body) + if err != nil { + return nil, fmt.Errorf("error fetching 'provenance file': %w", err) + } + } else { + helmBytes, err = ioutil.ReadFile(filepath.Clean(helm)) + if err != nil { + return nil, fmt.Errorf("error processing 'helm' file: %w", err) + } + } + if err := json.Unmarshal(helmBytes, &returnVal); err != nil { + return nil, fmt.Errorf("error parsing helm file: %w", err) + } + } else { + // we will need provenance file and public-key + re.HelmObj = models.HelmV001Schema{} + re.HelmObj.Chart = &models.HelmV001SchemaChart{} + re.HelmObj.Chart.Provenance = &models.HelmV001SchemaChartProvenance{} + + artifact := viper.GetString("artifact") + dataURL, err := url.Parse(artifact) + if err == nil && dataURL.IsAbs() { + re.HelmObj.Chart.Provenance.URL = strfmt.URI(artifact) + } else { + artifactBytes, err := ioutil.ReadFile(filepath.Clean(artifact)) + if err != nil { + return nil, fmt.Errorf("error reading artifact file: %w", err) + } + re.HelmObj.Chart.Provenance.Content = strfmt.Base64(artifactBytes) + } + + re.HelmObj.PublicKey = &models.HelmV001SchemaPublicKey{} + publicKey := viper.GetString("public-key") + keyURL, err := url.Parse(publicKey) + if err == nil && keyURL.IsAbs() { + re.HelmObj.PublicKey.URL = strfmt.URI(publicKey) + } else { + keyBytes, err := ioutil.ReadFile(filepath.Clean(publicKey)) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + re.HelmObj.PublicKey.Content = strfmt.Base64(keyBytes) + } + + if err := re.Validate(); err != nil { + return nil, err + } + + if re.HasExternalEntities() { + if err := re.FetchExternalEntities(context.Background()); err != nil { + return nil, fmt.Errorf("error retrieving external entities: %v", err) + } + } + + returnVal.APIVersion = swag.String(re.APIVersion()) + returnVal.Spec = re.HelmObj + } + + return &returnVal, nil +} + func CreateJarFromPFlags() (models.ProposedEntry, error) { //TODO: how to select version of item to create returnVal := models.Jar{} @@ -559,6 +637,7 @@ func (t *typeFlag) Set(s string) error { "intoto": {}, "rfc3161": {}, "alpine": {}, + "helm": {}, } if _, ok := set[s]; ok { t.value = s diff --git a/cmd/rekor-cli/app/pflags_test.go b/cmd/rekor-cli/app/pflags_test.go index 5f5f782aa..d56e7c8da 100644 --- a/cmd/rekor-cli/app/pflags_test.go +++ b/cmd/rekor-cli/app/pflags_test.go @@ -70,6 +70,8 @@ func TestArtifactPFlags(t *testing.T) { file, err = ioutil.ReadFile("../../../tests/test_alpine.pub") case "/alpineEntry": file, err = ioutil.ReadFile("../../../tests/alpine.json") + case "/helmEntry": + file, err = ioutil.ReadFile("../../../tests/helm.json") case "/not_found": err = errors.New("file not found") } @@ -123,6 +125,14 @@ func TestArtifactPFlags(t *testing.T) { expectParseSuccess: true, expectValidateSuccess: true, }, + { + caseDesc: "valid helm URL", + entry: testServer.URL + "/helmEntry", + typeStr: "helm", + expectParseSuccess: true, + expectValidateSuccess: true, + }, + { caseDesc: "valid rpm file, wrong type", typeStr: "rekord", @@ -401,6 +411,8 @@ func TestArtifactPFlags(t *testing.T) { createFn = CreateRFC3161FromPFlags case "alpine": createFn = CreateAlpineFromPFlags + case "helm": + createFn = CreateHelmFromPFlags default: t.Fatalf("type %v not implemented", tc.typeStr) } diff --git a/cmd/rekor-cli/app/upload.go b/cmd/rekor-cli/app/upload.go index 682fd04ac..0b54c9370 100644 --- a/cmd/rekor-cli/app/upload.go +++ b/cmd/rekor-cli/app/upload.go @@ -106,6 +106,11 @@ var uploadCmd = &cobra.Command{ if err != nil { return nil, err } + case "helm": + entry, err = CreateHelmFromPFlags() + if err != nil { + return nil, err + } default: return nil, errors.New("unknown type specified") } diff --git a/cmd/rekor-server/app/serve.go b/cmd/rekor-server/app/serve.go index 278b926e8..143dee77a 100644 --- a/cmd/rekor-server/app/serve.go +++ b/cmd/rekor-server/app/serve.go @@ -30,6 +30,8 @@ import ( "github.com/sigstore/rekor/pkg/log" "github.com/sigstore/rekor/pkg/types/alpine" alpine_v001 "github.com/sigstore/rekor/pkg/types/alpine/v0.0.1" + "github.com/sigstore/rekor/pkg/types/helm" + helm_v001 "github.com/sigstore/rekor/pkg/types/helm/v0.0.1" "github.com/sigstore/rekor/pkg/types/intoto" intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" "github.com/sigstore/rekor/pkg/types/jar" @@ -82,6 +84,7 @@ var serveCmd = &cobra.Command{ intoto.KIND: intoto_v001.APIVERSION, rfc3161.KIND: rfc3161_v001.APIVERSION, alpine.KIND: alpine_v001.APIVERSION, + helm.KIND: helm_v001.APIVERSION, } for k, v := range pluggableTypeMap { diff --git a/openapi.yaml b/openapi.yaml index 0e2b04c81..661527488 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -346,6 +346,22 @@ definitions: - spec additionalProperties: false + helm: + type: object + description: Helm chart + allOf: + - $ref: '#/definitions/ProposedEntry' + - properties: + apiVersion: + type: string + pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + spec: + type: object + $ref: 'pkg/types/helm/helm_schema.json' + required: + - apiVersion + - spec + intoto: type: object description: Intoto object diff --git a/pkg/generated/models/helm.go b/pkg/generated/models/helm.go new file mode 100644 index 000000000..d19b8bc8c --- /dev/null +++ b/pkg/generated/models/helm.go @@ -0,0 +1,210 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// Helm Helm chart +// +// swagger:model helm +type Helm struct { + + // api version + // Required: true + // Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + APIVersion *string `json:"apiVersion"` + + // spec + // Required: true + Spec HelmSchema `json:"spec"` +} + +// Kind gets the kind of this subtype +func (m *Helm) Kind() string { + return "helm" +} + +// SetKind sets the kind of this subtype +func (m *Helm) SetKind(val string) { +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *Helm) UnmarshalJSON(raw []byte) error { + var data struct { + + // api version + // Required: true + // Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + APIVersion *string `json:"apiVersion"` + + // spec + // Required: true + Spec HelmSchema `json:"spec"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + Kind string `json:"kind"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result Helm + + if base.Kind != result.Kind() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid kind value: %q", base.Kind) + } + + result.APIVersion = data.APIVersion + result.Spec = data.Spec + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m Helm) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // api version + // Required: true + // Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + APIVersion *string `json:"apiVersion"` + + // spec + // Required: true + Spec HelmSchema `json:"spec"` + }{ + + APIVersion: m.APIVersion, + + Spec: m.Spec, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + Kind string `json:"kind"` + }{ + + Kind: m.Kind(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this helm +func (m *Helm) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAPIVersion(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSpec(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Helm) validateAPIVersion(formats strfmt.Registry) error { + + if err := validate.Required("apiVersion", "body", m.APIVersion); err != nil { + return err + } + + if err := validate.Pattern("apiVersion", "body", *m.APIVersion, `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`); err != nil { + return err + } + + return nil +} + +func (m *Helm) validateSpec(formats strfmt.Registry) error { + + if m.Spec == nil { + return errors.Required("spec", "body", nil) + } + + return nil +} + +// ContextValidate validate this helm based on the context it is used +func (m *Helm) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// MarshalBinary interface implementation +func (m *Helm) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Helm) UnmarshalBinary(b []byte) error { + var res Helm + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/generated/models/helm_schema.go b/pkg/generated/models/helm_schema.go new file mode 100644 index 000000000..a6981eb63 --- /dev/null +++ b/pkg/generated/models/helm_schema.go @@ -0,0 +1,29 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +// HelmSchema Helm Schema +// +// Schema for Helm objects +// +// swagger:model helmSchema +type HelmSchema interface{} diff --git a/pkg/generated/models/helm_v001_schema.go b/pkg/generated/models/helm_v001_schema.go new file mode 100644 index 000000000..0d9e9d821 --- /dev/null +++ b/pkg/generated/models/helm_v001_schema.go @@ -0,0 +1,634 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// HelmV001Schema Helm v0.0.1 Schema +// +// Schema for Helm object +// +// swagger:model helmV001Schema +type HelmV001Schema struct { + + // chart + // Required: true + Chart *HelmV001SchemaChart `json:"chart"` + + // Arbitrary content to be included in the verifiable entry in the transparency log + ExtraData interface{} `json:"extraData,omitempty"` + + // public key + // Required: true + PublicKey *HelmV001SchemaPublicKey `json:"publicKey"` +} + +// Validate validates this helm v001 schema +func (m *HelmV001Schema) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateChart(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePublicKey(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *HelmV001Schema) validateChart(formats strfmt.Registry) error { + + if err := validate.Required("chart", "body", m.Chart); err != nil { + return err + } + + if m.Chart != nil { + if err := m.Chart.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("chart") + } + return err + } + } + + return nil +} + +func (m *HelmV001Schema) validatePublicKey(formats strfmt.Registry) error { + + if err := validate.Required("publicKey", "body", m.PublicKey); err != nil { + return err + } + + if m.PublicKey != nil { + if err := m.PublicKey.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("publicKey") + } + return err + } + } + + return nil +} + +// ContextValidate validate this helm v001 schema based on the context it is used +func (m *HelmV001Schema) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateChart(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidatePublicKey(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *HelmV001Schema) contextValidateChart(ctx context.Context, formats strfmt.Registry) error { + + if m.Chart != nil { + if err := m.Chart.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("chart") + } + return err + } + } + + return nil +} + +func (m *HelmV001Schema) contextValidatePublicKey(ctx context.Context, formats strfmt.Registry) error { + + if m.PublicKey != nil { + if err := m.PublicKey.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("publicKey") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *HelmV001Schema) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *HelmV001Schema) UnmarshalBinary(b []byte) error { + var res HelmV001Schema + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// HelmV001SchemaChart Information about the Helm chart associated with the entry +// +// swagger:model HelmV001SchemaChart +type HelmV001SchemaChart struct { + + // hash + Hash *HelmV001SchemaChartHash `json:"hash,omitempty"` + + // provenance + // Required: true + Provenance *HelmV001SchemaChartProvenance `json:"provenance"` +} + +// Validate validates this helm v001 schema chart +func (m *HelmV001SchemaChart) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateHash(formats); err != nil { + res = append(res, err) + } + + if err := m.validateProvenance(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *HelmV001SchemaChart) validateHash(formats strfmt.Registry) error { + if swag.IsZero(m.Hash) { // not required + return nil + } + + if m.Hash != nil { + if err := m.Hash.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("chart" + "." + "hash") + } + return err + } + } + + return nil +} + +func (m *HelmV001SchemaChart) validateProvenance(formats strfmt.Registry) error { + + if err := validate.Required("chart"+"."+"provenance", "body", m.Provenance); err != nil { + return err + } + + if m.Provenance != nil { + if err := m.Provenance.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("chart" + "." + "provenance") + } + return err + } + } + + return nil +} + +// ContextValidate validate this helm v001 schema chart based on the context it is used +func (m *HelmV001SchemaChart) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateHash(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateProvenance(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *HelmV001SchemaChart) contextValidateHash(ctx context.Context, formats strfmt.Registry) error { + + if m.Hash != nil { + if err := m.Hash.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("chart" + "." + "hash") + } + return err + } + } + + return nil +} + +func (m *HelmV001SchemaChart) contextValidateProvenance(ctx context.Context, formats strfmt.Registry) error { + + if m.Provenance != nil { + if err := m.Provenance.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("chart" + "." + "provenance") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *HelmV001SchemaChart) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *HelmV001SchemaChart) UnmarshalBinary(b []byte) error { + var res HelmV001SchemaChart + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// HelmV001SchemaChartHash Specifies the hash algorithm and value for the chart +// +// swagger:model HelmV001SchemaChartHash +type HelmV001SchemaChartHash struct { + + // The hashing function used to compute the hash value + // Required: true + // Enum: [sha256] + Algorithm *string `json:"algorithm"` + + // The hash value for the chart + // Required: true + Value *string `json:"value"` +} + +// Validate validates this helm v001 schema chart hash +func (m *HelmV001SchemaChartHash) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateAlgorithm(formats); err != nil { + res = append(res, err) + } + + if err := m.validateValue(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var helmV001SchemaChartHashTypeAlgorithmPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["sha256"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + helmV001SchemaChartHashTypeAlgorithmPropEnum = append(helmV001SchemaChartHashTypeAlgorithmPropEnum, v) + } +} + +const ( + + // HelmV001SchemaChartHashAlgorithmSha256 captures enum value "sha256" + HelmV001SchemaChartHashAlgorithmSha256 string = "sha256" +) + +// prop value enum +func (m *HelmV001SchemaChartHash) validateAlgorithmEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, helmV001SchemaChartHashTypeAlgorithmPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *HelmV001SchemaChartHash) validateAlgorithm(formats strfmt.Registry) error { + + if err := validate.Required("chart"+"."+"hash"+"."+"algorithm", "body", m.Algorithm); err != nil { + return err + } + + // value enum + if err := m.validateAlgorithmEnum("chart"+"."+"hash"+"."+"algorithm", "body", *m.Algorithm); err != nil { + return err + } + + return nil +} + +func (m *HelmV001SchemaChartHash) validateValue(formats strfmt.Registry) error { + + if err := validate.Required("chart"+"."+"hash"+"."+"value", "body", m.Value); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this helm v001 schema chart hash based on context it is used +func (m *HelmV001SchemaChartHash) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *HelmV001SchemaChartHash) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *HelmV001SchemaChartHash) UnmarshalBinary(b []byte) error { + var res HelmV001SchemaChartHash + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// HelmV001SchemaChartProvenance The provenance entry associated with the signed Helm Chart +// +// swagger:model HelmV001SchemaChartProvenance +type HelmV001SchemaChartProvenance struct { + + // Specifies the content of the provenance file inline within the document + // Format: byte + Content strfmt.Base64 `json:"content,omitempty"` + + // signature + Signature *HelmV001SchemaChartProvenanceSignature `json:"signature,omitempty"` + + // Specifies the location of the provenance file + // Format: uri + URL strfmt.URI `json:"url,omitempty"` +} + +// Validate validates this helm v001 schema chart provenance +func (m *HelmV001SchemaChartProvenance) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateSignature(formats); err != nil { + res = append(res, err) + } + + if err := m.validateURL(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *HelmV001SchemaChartProvenance) validateSignature(formats strfmt.Registry) error { + if swag.IsZero(m.Signature) { // not required + return nil + } + + if m.Signature != nil { + if err := m.Signature.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("chart" + "." + "provenance" + "." + "signature") + } + return err + } + } + + return nil +} + +func (m *HelmV001SchemaChartProvenance) validateURL(formats strfmt.Registry) error { + if swag.IsZero(m.URL) { // not required + return nil + } + + if err := validate.FormatOf("chart"+"."+"provenance"+"."+"url", "body", "uri", m.URL.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this helm v001 schema chart provenance based on the context it is used +func (m *HelmV001SchemaChartProvenance) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateSignature(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *HelmV001SchemaChartProvenance) contextValidateSignature(ctx context.Context, formats strfmt.Registry) error { + + if m.Signature != nil { + if err := m.Signature.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("chart" + "." + "provenance" + "." + "signature") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *HelmV001SchemaChartProvenance) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *HelmV001SchemaChartProvenance) UnmarshalBinary(b []byte) error { + var res HelmV001SchemaChartProvenance + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// HelmV001SchemaChartProvenanceSignature Information about the included signature in the provenance file +// +// swagger:model HelmV001SchemaChartProvenanceSignature +type HelmV001SchemaChartProvenanceSignature struct { + + // Specifies the signature embedded within the provenance file + // Required: true + // Format: byte + Content *strfmt.Base64 `json:"content"` +} + +// Validate validates this helm v001 schema chart provenance signature +func (m *HelmV001SchemaChartProvenanceSignature) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateContent(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *HelmV001SchemaChartProvenanceSignature) validateContent(formats strfmt.Registry) error { + + if err := validate.Required("chart"+"."+"provenance"+"."+"signature"+"."+"content", "body", m.Content); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this helm v001 schema chart provenance signature based on context it is used +func (m *HelmV001SchemaChartProvenanceSignature) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *HelmV001SchemaChartProvenanceSignature) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *HelmV001SchemaChartProvenanceSignature) UnmarshalBinary(b []byte) error { + var res HelmV001SchemaChartProvenanceSignature + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// HelmV001SchemaPublicKey The public key that can verify the package signature +// +// swagger:model HelmV001SchemaPublicKey +type HelmV001SchemaPublicKey struct { + + // Specifies the content of the public key inline within the document + // Format: byte + Content strfmt.Base64 `json:"content,omitempty"` + + // Specifies the location of the public key + // Format: uri + URL strfmt.URI `json:"url,omitempty"` +} + +// Validate validates this helm v001 schema public key +func (m *HelmV001SchemaPublicKey) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateURL(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *HelmV001SchemaPublicKey) validateURL(formats strfmt.Registry) error { + if swag.IsZero(m.URL) { // not required + return nil + } + + if err := validate.FormatOf("publicKey"+"."+"url", "body", "uri", m.URL.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this helm v001 schema public key based on context it is used +func (m *HelmV001SchemaPublicKey) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *HelmV001SchemaPublicKey) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *HelmV001SchemaPublicKey) UnmarshalBinary(b []byte) error { + var res HelmV001SchemaPublicKey + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/generated/models/proposed_entry.go b/pkg/generated/models/proposed_entry.go index 94a5d6fab..c25459183 100644 --- a/pkg/generated/models/proposed_entry.go +++ b/pkg/generated/models/proposed_entry.go @@ -121,6 +121,12 @@ func unmarshalProposedEntry(data []byte, consumer runtime.Consumer) (ProposedEnt return nil, err } return &result, nil + case "helm": + var result Helm + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "intoto": var result Intoto if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index 69c9dd862..1e766ffbd 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -651,6 +651,31 @@ func init() { } ] }, + "helm": { + "description": "Helm chart", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ProposedEntry" + }, + { + "required": [ + "apiVersion", + "spec" + ], + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "spec": { + "type": "object", + "$ref": "pkg/types/helm/helm_schema.json" + } + } + } + ] + }, "intoto": { "description": "Intoto object", "type": "object", @@ -1389,6 +1414,183 @@ func init() { } } }, + "HelmV001SchemaChart": { + "description": "Information about the Helm chart associated with the entry", + "type": "object", + "required": [ + "provenance" + ], + "properties": { + "hash": { + "description": "Specifies the hash algorithm and value for the chart", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the chart", + "type": "string" + } + } + }, + "provenance": { + "description": "The provenance entry associated with the signed Helm Chart", + "type": "object", + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ], + "properties": { + "content": { + "description": "Specifies the content of the provenance file inline within the document", + "type": "string", + "format": "byte" + }, + "signature": { + "description": "Information about the included signature in the provenance file", + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "description": "Specifies the signature embedded within the provenance file ", + "type": "string", + "format": "byte" + } + } + }, + "url": { + "description": "Specifies the location of the provenance file", + "type": "string", + "format": "uri" + } + } + } + } + }, + "HelmV001SchemaChartHash": { + "description": "Specifies the hash algorithm and value for the chart", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the chart", + "type": "string" + } + } + }, + "HelmV001SchemaChartProvenance": { + "description": "The provenance entry associated with the signed Helm Chart", + "type": "object", + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ], + "properties": { + "content": { + "description": "Specifies the content of the provenance file inline within the document", + "type": "string", + "format": "byte" + }, + "signature": { + "description": "Information about the included signature in the provenance file", + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "description": "Specifies the signature embedded within the provenance file ", + "type": "string", + "format": "byte" + } + } + }, + "url": { + "description": "Specifies the location of the provenance file", + "type": "string", + "format": "uri" + } + } + }, + "HelmV001SchemaChartProvenanceSignature": { + "description": "Information about the included signature in the provenance file", + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "description": "Specifies the signature embedded within the provenance file ", + "type": "string", + "format": "byte" + } + } + }, + "HelmV001SchemaPublicKey": { + "description": "The public key that can verify the package signature", + "type": "object", + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ], + "properties": { + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + }, + "url": { + "description": "Specifies the location of the public key", + "type": "string", + "format": "uri" + } + } + }, "InclusionProof": { "type": "object", "required": [ @@ -2199,6 +2401,160 @@ func init() { "$schema": "http://json-schema.org/draft-07/schema", "$id": "http://rekor.sigstore.dev/types/alpine/alpine_v0_0_1_schema.json" }, + "helm": { + "description": "Helm chart", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ProposedEntry" + }, + { + "required": [ + "apiVersion", + "spec" + ], + "properties": { + "apiVersion": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "spec": { + "$ref": "#/definitions/helmSchema" + } + } + } + ] + }, + "helmSchema": { + "description": "Schema for Helm objects", + "type": "object", + "title": "Helm Schema", + "oneOf": [ + { + "$ref": "#/definitions/helmV001Schema" + } + ], + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://rekor.sigstore.dev/types/helm/helm_schema.json" + }, + "helmV001Schema": { + "description": "Schema for Helm object", + "type": "object", + "title": "Helm v0.0.1 Schema", + "required": [ + "publicKey", + "chart" + ], + "properties": { + "chart": { + "description": "Information about the Helm chart associated with the entry", + "type": "object", + "required": [ + "provenance" + ], + "properties": { + "hash": { + "description": "Specifies the hash algorithm and value for the chart", + "type": "object", + "required": [ + "algorithm", + "value" + ], + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the chart", + "type": "string" + } + } + }, + "provenance": { + "description": "The provenance entry associated with the signed Helm Chart", + "type": "object", + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ], + "properties": { + "content": { + "description": "Specifies the content of the provenance file inline within the document", + "type": "string", + "format": "byte" + }, + "signature": { + "description": "Information about the included signature in the provenance file", + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "description": "Specifies the signature embedded within the provenance file ", + "type": "string", + "format": "byte" + } + } + }, + "url": { + "description": "Specifies the location of the provenance file", + "type": "string", + "format": "uri" + } + } + } + } + }, + "extraData": { + "description": "Arbitrary content to be included in the verifiable entry in the transparency log", + "type": "object", + "additionalProperties": true + }, + "publicKey": { + "description": "The public key that can verify the package signature", + "type": "object", + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ], + "properties": { + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + }, + "url": { + "description": "Specifies the location of the public key", + "type": "string", + "format": "uri" + } + } + } + }, + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://rekor.sigstore.dev/types/helm/helm_v0_0_1_schema.json" + }, "intoto": { "description": "Intoto object", "type": "object", diff --git a/pkg/types/README.md b/pkg/types/README.md index b2b1f9d26..c7fead08d 100644 --- a/pkg/types/README.md +++ b/pkg/types/README.md @@ -102,7 +102,7 @@ func init() { } ``` -6. Add an entry to `pluggableTypeMap` in `cmd/server/app/serve.go` that provides a reference to your package. This ensures that the `init` 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. +6. Add an entry to `pluggableTypeMap` in `cmd/rekor-server/app/serve.go` that provides a reference to your package. This ensures that the `init` 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. 7. After adding sufficient unit & integration tests, submit a pull request to `github.com/sigstore/rekor` for review and addition to the codebase. @@ -118,6 +118,6 @@ To add new version of the default `Rekord` type: 4. In your package's `init` method, ensure there is a call to `VersionMap.Store()` 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. -5. Add an entry to `pluggableTypeMap` in `cmd/server/app/serve.go` that provides a reference to the Go package implementing the new version. This ensures that the `init` 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. +5. Add an entry to `pluggableTypeMap` in `cmd/rekor-server/app/serve.go` that provides a reference to the Go package implementing the new version. This ensures that the `init` 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. 6. After adding sufficient unit & integration tests, submit a pull request to `github.com/sigstore/rekor` for review and addition to the codebase. diff --git a/pkg/types/helm/helm.go b/pkg/types/helm/helm.go new file mode 100644 index 000000000..6c20e6f83 --- /dev/null +++ b/pkg/types/helm/helm.go @@ -0,0 +1,57 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helm + +import ( + "errors" + + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" +) + +const ( + KIND = "helm" +) + +type BaseHelmType struct { + types.RekorType +} + +func init() { + types.TypeMap.Store(KIND, New) +} + +func New() types.TypeImpl { + bit := BaseHelmType{} + bit.Kind = KIND + bit.VersionMap = VersionMap + return &bit +} + +var VersionMap = types.NewSemVerEntryFactoryMap() + +func (it BaseHelmType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImpl, error) { + if pe == nil { + return nil, errors.New("proposed entry cannot be nil") + } + + in, ok := pe.(*models.Helm) + if !ok { + return nil, errors.New("cannot unmarshal non-Rekord types") + } + + return it.VersionedUnmarshal(in, *in.APIVersion) +} diff --git a/pkg/types/helm/helm_schema.json b/pkg/types/helm/helm_schema.json new file mode 100644 index 000000000..a380fbe54 --- /dev/null +++ b/pkg/types/helm/helm_schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/helm/helm_schema.json", + "title": "Helm Schema", + "description": "Schema for Helm objects", + "type": "object", + "oneOf": [ + { + "$ref": "v0.0.1/helm_v0_0_1_schema.json" + } + ] +} \ No newline at end of file diff --git a/pkg/types/helm/helm_test.go b/pkg/types/helm/helm_test.go new file mode 100644 index 000000000..33ff86dc8 --- /dev/null +++ b/pkg/types/helm/helm_test.go @@ -0,0 +1,128 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helm + +import ( + "context" + "errors" + "testing" + + "github.com/go-openapi/swag" + + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/types" +) + +type UnmarshalTester struct { + models.Helm +} + +func (u UnmarshalTester) NewEntry() types.EntryImpl { + return &UnmarshalTester{} +} + +func (u UnmarshalTester) Validate() error { + return nil +} + +func (u UnmarshalTester) APIVersion() string { + return "2.0.1" +} + +func (u UnmarshalTester) IndexKeys() []string { + return []string{} +} + +func (u UnmarshalTester) Canonicalize(ctx context.Context) ([]byte, error) { + return nil, nil +} + +func (u UnmarshalTester) HasExternalEntities() bool { + return false +} + +func (u *UnmarshalTester) FetchExternalEntities(ctx context.Context) error { + return nil +} + +func (u UnmarshalTester) Unmarshal(pe models.ProposedEntry) error { + return nil +} + +func (u UnmarshalTester) Attestation() (string, []byte) { + return "", nil +} + +type UnmarshalFailsTester struct { + UnmarshalTester +} + +func (u UnmarshalFailsTester) NewEntry() types.EntryImpl { + return &UnmarshalFailsTester{} +} + +func (u UnmarshalFailsTester) Unmarshal(pe models.ProposedEntry) error { + return errors.New("error") +} + +func TestHelmType(t *testing.T) { + // empty to start + if VersionMap.Count() != 0 { + t.Error("semver range was not blank at start of test") + } + + u := UnmarshalTester{} + // ensure semver range parser is working + invalidSemVerRange := "not a valid semver range" + err := VersionMap.SetEntryFactory(invalidSemVerRange, u.NewEntry) + if err == nil || VersionMap.Count() > 0 { + t.Error("invalid semver range was incorrectly added to SemVerToFacFnMap") + } + + // valid semver range can be parsed + err = VersionMap.SetEntryFactory(">= 1.2.3", u.NewEntry) + if err != nil || VersionMap.Count() != 1 { + t.Error("valid semver range was not added to SemVerToFacFnMap") + } + + u.Helm.APIVersion = swag.String("2.0.1") + brt := New() + + // version requested matches implementation in map + if _, err := brt.UnmarshalEntry(&u.Helm); err != nil { + t.Errorf("unexpected error in Unmarshal: %v", err) + } + + // version requested fails to match implementation in map + u.Helm.APIVersion = swag.String("1.2.2") + if _, err := brt.UnmarshalEntry(&u.Helm); err == nil { + t.Error("unexpected success in Unmarshal for non-matching version") + } + + // error in Unmarshal call is raised appropriately + u.Helm.APIVersion = swag.String("2.2.0") + u2 := UnmarshalFailsTester{} + _ = VersionMap.SetEntryFactory(">= 1.2.3", u2.NewEntry) + if _, err := brt.UnmarshalEntry(&u.Helm); err == nil { + t.Error("unexpected success in Unmarshal when error is thrown") + } + + // version requested fails to match implementation in map + u.Helm.APIVersion = swag.String("not_a_version") + if _, err := brt.UnmarshalEntry(&u.Helm); err == nil { + t.Error("unexpected success in Unmarshal for invalid version") + } +} diff --git a/pkg/types/helm/provenance_test.go b/pkg/types/helm/provenance_test.go new file mode 100644 index 000000000..50fac2e7d --- /dev/null +++ b/pkg/types/helm/provenance_test.go @@ -0,0 +1,73 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helm + +import ( + "bytes" + "os" + "testing" + + "github.com/sigstore/rekor/pkg/pki" + "github.com/sigstore/rekor/pkg/pki/pgp" +) + +func TestProvenance(t *testing.T) { + inputProvenance, err := os.Open("../../../tests/test-0.1.0.tgz.prov") + if err != nil { + t.Fatalf("could not open provenance file %v", err) + } + + provenance := Provenance{} + err = provenance.Unmarshal(inputProvenance) + + if err != nil { + t.Fatalf("unmarshal error: %v", err) + } + + checksum, err := provenance.GetChartHash() + + if err != nil { + t.Fatalf("Error retrieving chart hash: %v", err) + } + + if len(checksum) == 0 { + t.Fatal("Empty checksum") + } + + publickeyFile, err := os.Open("../../../tests/test_helm_armor.pub") + if err != nil { + t.Fatalf("could not open public key %v", err) + } + + publicKey, err := pgp.NewPublicKey(publickeyFile) + if err != nil { + t.Fatalf("failed to parse public key: %v", err) + } + + artifactFactory := pki.NewArtifactFactory("pgp") + sig, err := artifactFactory.NewSignature(provenance.Block.ArmoredSignature.Body) + + if err != nil { + t.Fatalf("Failed to create signature %v", err) + } + + err = sig.Verify(bytes.NewBuffer(provenance.Block.Bytes), publicKey) + + if err != nil { + t.Fatalf("Failed to verify signature %v", err) + } + +} diff --git a/pkg/types/helm/providence.go b/pkg/types/helm/providence.go new file mode 100644 index 000000000..697b07493 --- /dev/null +++ b/pkg/types/helm/providence.go @@ -0,0 +1,120 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helm + +import ( + "bytes" + "io" + "strings" + + "github.com/ghodss/yaml" + "github.com/pkg/errors" + + "golang.org/x/crypto/openpgp/clearsign" +) + +type Provenance struct { + ChartMetadata map[string]string + SumCollection *SumCollection + Block *clearsign.Block +} + +type SumCollection struct { + Files map[string]string `json:"files"` + Images map[string]string `json:"images,omitempty"` +} + +func (p *Provenance) Unmarshal(reader io.Reader) error { + + buf := &bytes.Buffer{} + read, err := buf.ReadFrom(reader) + + if err != nil { + return errors.New("Failed to read from buffer") + } + + if read == 0 { + return errors.New("Provenance file contains no content") + } + + rawProvenanceFile := buf.Bytes() + + block, _ := clearsign.Decode(rawProvenanceFile) + + if block == nil { + return errors.New("Unable to decode provenance file") + } + + if block.ArmoredSignature == nil { + return errors.New("Unable to locate armored signature in provenance file") + } + + p.Block = block + + err = p.parseMessageBlock(block.Plaintext) + + if err != nil { + return errors.Wrap(err, "Error occurred parsing message block") + } + + return nil + +} + +func (p *Provenance) parseMessageBlock(data []byte) error { + + parts := bytes.Split(data, []byte("\n...\n")) + if len(parts) < 2 { + return errors.New("message block must have at least two parts") + } + + sc := &SumCollection{} + + err := yaml.Unmarshal(parts[1], sc) + + if err != nil { + return errors.Wrap(err, "Error occurred parsing SumCollection") + } + + p.SumCollection = sc + + return nil +} + +func (p *Provenance) GetChartHash() (string, error) { + + if p.SumCollection == nil || p.SumCollection.Files == nil { + return "", errors.New("Unable to locate chart hash") + + } + + files := p.SumCollection.Files + + for _, value := range files { + + parts := strings.Split(value, ":") + + if len(parts) != 2 { + return "", errors.New("Invalid hash found in Provenance file") + } + + return parts[1], nil + } + + // Return error if no keys found + return "", errors.New("No checksums found") + +} diff --git a/pkg/types/helm/v0.0.1/entry.go b/pkg/types/helm/v0.0.1/entry.go new file mode 100644 index 000000000..36a82729a --- /dev/null +++ b/pkg/types/helm/v0.0.1/entry.go @@ -0,0 +1,349 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helm + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "strings" + + "github.com/pkg/errors" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/sigstore/rekor/pkg/generated/models" + "github.com/sigstore/rekor/pkg/log" + "github.com/sigstore/rekor/pkg/pki" + "github.com/sigstore/rekor/pkg/pki/pgp" + "github.com/sigstore/rekor/pkg/types" + "github.com/sigstore/rekor/pkg/types/helm" + "github.com/sigstore/rekor/pkg/util" + "golang.org/x/sync/errgroup" +) + +const ( + APIVERSION = "0.0.1" +) + +func init() { + if err := helm.VersionMap.SetEntryFactory(APIVERSION, NewEntry); err != nil { + log.Logger.Panic(err) + } +} + +type V001Entry struct { + HelmObj models.HelmV001Schema + fetchedExternalEntities bool + keyObj pki.PublicKey + sigObj pki.Signature + provenanceObj *helm.Provenance +} + +func (v V001Entry) APIVersion() string { + return APIVERSION +} + +func NewEntry() types.EntryImpl { + return &V001Entry{} +} + +func (v V001Entry) IndexKeys() []string { + var result []string + + if v.HasExternalEntities() { + if err := v.FetchExternalEntities(context.Background()); err != nil { + log.Logger.Error(err) + return result + } + } + + key, err := v.keyObj.CanonicalValue() + if err != nil { + log.Logger.Error(err) + } else { + keyHash := sha256.Sum256(key) + result = append(result, strings.ToLower(hex.EncodeToString(keyHash[:]))) + } + + result = append(result, v.keyObj.EmailAddresses()...) + + chartHash, err := v.provenanceObj.GetChartHash() + + if err != nil { + log.Logger.Error(err) + } else { + result = append(result, chartHash) + } + + //TODO: Store signature as index + + return result +} + +func (v *V001Entry) Unmarshal(pe models.ProposedEntry) error { + + helm, ok := pe.(*models.Helm) + if !ok { + return errors.New("cannot unmarshal non Helm v0.0.1 type") + } + + if err := types.DecodeEntry(helm.Spec, &v.HelmObj); err != nil { + return err + } + + // field validation + if err := v.HelmObj.Validate(strfmt.Default); err != nil { + return err + } + // cross field validation + return nil + +} + +func (v V001Entry) HasExternalEntities() bool { + + if v.fetchedExternalEntities { + return false + } + + if v.HelmObj.PublicKey != nil && v.HelmObj.PublicKey.URL.String() != "" { + return true + } + if v.HelmObj.Chart != nil && v.HelmObj.Chart.Provenance != nil && v.HelmObj.Chart.Provenance.URL.String() != "" { + return true + } + + return false +} + +func (v *V001Entry) FetchExternalEntities(ctx context.Context) error { + + if v.fetchedExternalEntities { + return nil + } + + if err := v.Validate(); err != nil { + return err + } + + g, ctx := errgroup.WithContext(ctx) + + provenanceR, provenanceW := io.Pipe() + + defer provenanceR.Close() + + closePipesOnError := func(err error) error { + pipeReaders := []*io.PipeReader{provenanceR} + pipeWriters := []*io.PipeWriter{provenanceW} + for idx := range pipeReaders { + if e := pipeReaders[idx].CloseWithError(err); e != nil { + log.Logger.Error(fmt.Errorf("error closing pipe: %w", e)) + } + if e := pipeWriters[idx].CloseWithError(err); e != nil { + log.Logger.Error(fmt.Errorf("error closing pipe: %w", e)) + } + } + return err + } + + artifactFactory := pki.NewArtifactFactory("pgp") + + g.Go(func() error { + defer provenanceW.Close() + + dataReadCloser, err := util.FileOrURLReadCloser(ctx, v.HelmObj.Chart.Provenance.URL.String(), v.HelmObj.Chart.Provenance.Content) + if err != nil { + return closePipesOnError(err) + } + defer dataReadCloser.Close() + + /* #nosec G110 */ + if _, err := io.Copy(provenanceW, dataReadCloser); err != nil { + return closePipesOnError(err) + } + return nil + }) + + keyResult := make(chan *pgp.PublicKey) + + g.Go(func() error { + defer close(keyResult) + keyReadCloser, err := util.FileOrURLReadCloser(ctx, v.HelmObj.PublicKey.URL.String(), + v.HelmObj.PublicKey.Content) + if err != nil { + return closePipesOnError(err) + } + defer keyReadCloser.Close() + + v.keyObj, err = artifactFactory.NewPublicKey(keyReadCloser) + + if err != nil { + return closePipesOnError(err) + } + + select { + case <-ctx.Done(): + return ctx.Err() + case keyResult <- v.keyObj.(*pgp.PublicKey): + return nil + } + }) + + g.Go(func() error { + + provenance := helm.Provenance{} + if err := provenance.Unmarshal(provenanceR); err != nil { + return closePipesOnError(err) + } + + key := <-keyResult + if key == nil { + return closePipesOnError(errors.New("error processing public key")) + } + + // Set signature + sig, err := artifactFactory.NewSignature(provenance.Block.ArmoredSignature.Body) + + if err != nil { + return closePipesOnError(err) + } + + // Verify signature + if err := sig.Verify(bytes.NewReader(provenance.Block.Bytes), key); err != nil { + return closePipesOnError(err) + } + + v.sigObj = sig + v.provenanceObj = &provenance + + select { + case <-ctx.Done(): + return ctx.Err() + default: + return nil + } + }) + + if err := g.Wait(); err != nil { + return err + } + + v.fetchedExternalEntities = true + return nil + +} + +func (v *V001Entry) Canonicalize(ctx context.Context) ([]byte, error) { + + if err := v.FetchExternalEntities(ctx); err != nil { + return nil, err + } + + if v.keyObj == nil { + return nil, errors.New("key object not initialized before canonicalization") + } + + canonicalEntry := models.HelmV001Schema{} + canonicalEntry.ExtraData = v.HelmObj.ExtraData + + var err error + + canonicalEntry.PublicKey = &models.HelmV001SchemaPublicKey{} + keyContent, err := v.keyObj.CanonicalValue() + if err != nil { + return nil, err + } + + canonicalEntry.PublicKey.Content = (strfmt.Base64)(keyContent) + + canonicalEntry.Chart = &models.HelmV001SchemaChart{} + + chartHash, err := v.provenanceObj.GetChartHash() + + if err != nil { + return nil, err + } + + sha256 := models.AlpineV001SchemaPackageHashAlgorithmSha256 + + canonicalEntry.Chart.Hash = &models.HelmV001SchemaChartHash{} + canonicalEntry.Chart.Hash.Algorithm = &sha256 + canonicalEntry.Chart.Hash.Value = &chartHash + + canonicalEntry.Chart.Provenance = &models.HelmV001SchemaChartProvenance{} + canonicalEntry.Chart.Provenance.Signature = &models.HelmV001SchemaChartProvenanceSignature{} + + sigContent, err := v.sigObj.CanonicalValue() + if err != nil { + return nil, err + } + + canonicalEntry.Chart.Provenance.Signature.Content = (*strfmt.Base64)(&sigContent) + + // wrap in valid object with kind and apiVersion set + helmObj := models.Helm{} + helmObj.APIVersion = swag.String(APIVERSION) + helmObj.Spec = &canonicalEntry + + bytes, err := json.Marshal(&helmObj) + if err != nil { + return nil, err + } + + return bytes, nil + +} + +// Validate performs cross-field validation for fields in object +func (v V001Entry) Validate() error { + + key := v.HelmObj.PublicKey + + if key == nil { + return errors.New("missing public key") + } + + if len(key.Content) == 0 && key.URL.String() == "" { + return errors.New("one of 'content' or 'url' must be specified for publicKey") + } + + chart := v.HelmObj.Chart + + if chart == nil { + return errors.New("missing chart") + } + + provenance := chart.Provenance + + if provenance == nil { + return errors.New("missing provenance") + } + + if len(provenance.Content) == 0 && provenance.URL.String() == "" { + return errors.New("one of 'content' or 'url' must be specified for provenance") + } + + return nil +} + +func (v V001Entry) Attestation() (string, []byte) { + return "", nil +} diff --git a/pkg/types/helm/v0.0.1/entry_test.go b/pkg/types/helm/v0.0.1/entry_test.go new file mode 100644 index 000000000..6fce3c2bd --- /dev/null +++ b/pkg/types/helm/v0.0.1/entry_test.go @@ -0,0 +1,250 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package helm + +import ( + "context" + "errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "go.uber.org/goleak" + + "github.com/sigstore/rekor/pkg/generated/models" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + +func TestNewEntryReturnType(t *testing.T) { + entry := NewEntry() + if reflect.TypeOf(entry) != reflect.ValueOf(&V001Entry{}).Type() { + t.Errorf("invalid type returned from NewEntry: %T", entry) + } +} + +func TestCrossFieldValidation(t *testing.T) { + type TestCase struct { + caseDesc string + entry V001Entry + hasExtEntities bool + expectUnmarshalSuccess bool + expectCanonicalizeSuccess bool + } + + keyBytes, _ := ioutil.ReadFile("../../../../tests/test_helm_armor.pub") + provenanceBytes, _ := ioutil.ReadFile("../../../../tests/test-0.1.0.tgz.prov") + + testServer := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + var file *[]byte + var err error + + switch r.URL.Path { + case "/key": + file = &keyBytes + case "/provenance": + file = &provenanceBytes + default: + err = errors.New("unknown URL") + } + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + w.WriteHeader(http.StatusOK) + _, _ = w.Write(*file) + })) + defer testServer.Close() + + testCases := []TestCase{ + { + caseDesc: "empty obj", + entry: V001Entry{}, + expectUnmarshalSuccess: false, + }, + + { + caseDesc: "provenance file without public key", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64(provenanceBytes), + }, + }, + }, + }, + expectUnmarshalSuccess: false, + }, + { + caseDesc: "public key without provenance file", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: strfmt.Base64(keyBytes), + }, + }, + }, + expectUnmarshalSuccess: false, + }, + { + caseDesc: "public key with empty provenance file", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + PublicKey: &models.HelmV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/key"), + }, + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{}, + }, + }, + }, + expectUnmarshalSuccess: false, + hasExtEntities: true, + }, + { + caseDesc: "provenance file with 404 on public key", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + PublicKey: &models.HelmV001SchemaPublicKey{ + URL: strfmt.URI(testServer.URL + "/404"), + }, + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64(provenanceBytes), + }, + }, + }, + }, + expectUnmarshalSuccess: true, + hasExtEntities: true, + expectCanonicalizeSuccess: false, + }, + { + caseDesc: "public key with 404 on provenance file", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: strfmt.Base64(keyBytes), + }, + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + URL: strfmt.URI(testServer.URL + "/404"), + }, + }, + }, + }, + expectUnmarshalSuccess: true, + hasExtEntities: true, + expectCanonicalizeSuccess: false, + }, + { + caseDesc: "public key and invalid provenance content", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: strfmt.Base64(keyBytes), + }, + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64(keyBytes), + }, + }, + }, + }, + expectUnmarshalSuccess: true, + hasExtEntities: false, + expectCanonicalizeSuccess: false, + }, + { + caseDesc: "provenance content with invalid public key", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: strfmt.Base64(provenanceBytes), + }, + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64(provenanceBytes), + }, + }, + }, + }, + expectUnmarshalSuccess: true, + hasExtEntities: false, + expectCanonicalizeSuccess: false, + }, + { + caseDesc: "provenance content with valid public key", + entry: V001Entry{ + HelmObj: models.HelmV001Schema{ + PublicKey: &models.HelmV001SchemaPublicKey{ + Content: strfmt.Base64(keyBytes), + }, + Chart: &models.HelmV001SchemaChart{ + Provenance: &models.HelmV001SchemaChartProvenance{ + Content: strfmt.Base64(provenanceBytes), + }, + }, + }, + }, + expectUnmarshalSuccess: true, + hasExtEntities: false, + expectCanonicalizeSuccess: true, + }, + } + + for _, tc := range testCases { + if err := tc.entry.Validate(); (err == nil) != tc.expectUnmarshalSuccess { + t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) + } + + v := &V001Entry{} + r := models.Helm{ + APIVersion: swag.String(tc.entry.APIVersion()), + Spec: tc.entry.HelmObj, + } + + unmarshalAndValidate := func() error { + if err := v.Unmarshal(&r); err != nil { + return err + } + if err := v.Validate(); err != nil { + return err + } + return nil + } + + if err := unmarshalAndValidate(); (err == nil) != tc.expectUnmarshalSuccess { + t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err) + } + + if tc.entry.HasExternalEntities() != tc.hasExtEntities { + t.Errorf("unexpected result from HasExternalEntities for '%v'", tc.caseDesc) + } + + if _, err := tc.entry.Canonicalize(context.TODO()); (err == nil) != tc.expectCanonicalizeSuccess { + t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err) + } + } +} diff --git a/pkg/types/helm/v0.0.1/helm_v0_0_1_schema.json b/pkg/types/helm/v0.0.1/helm_v0_0_1_schema.json new file mode 100644 index 000000000..4cbfd6f56 --- /dev/null +++ b/pkg/types/helm/v0.0.1/helm_v0_0_1_schema.json @@ -0,0 +1,118 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://rekor.sigstore.dev/types/helm/helm_v0_0_1_schema.json", + "title": "Helm v0.0.1 Schema", + "description": "Schema for Helm object", + "type": "object", + "properties": { + "publicKey": { + "description": "The public key that can verify the package signature", + "type": "object", + "properties": { + "url": { + "description": "Specifies the location of the public key", + "type": "string", + "format": "uri" + }, + "content": { + "description": "Specifies the content of the public key inline within the document", + "type": "string", + "format": "byte" + } + }, + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ] + }, + "chart": { + "description": "Information about the Helm chart associated with the entry", + "type": "object", + "properties": { + "hash": { + "description": "Specifies the hash algorithm and value for the chart", + "type": "object", + "properties": { + "algorithm": { + "description": "The hashing function used to compute the hash value", + "type": "string", + "enum": [ + "sha256" + ] + }, + "value": { + "description": "The hash value for the chart", + "type": "string" + } + }, + "required": [ + "algorithm", + "value" + ] + }, + "provenance": { + "description": "The provenance entry associated with the signed Helm Chart", + "type": "object", + "properties": { + "signature": { + "description": "Information about the included signature in the provenance file", + "type": "object", + "properties": { + "content": { + "description": "Specifies the signature embedded within the provenance file ", + "type": "string", + "format": "byte" + } + }, + "required": [ + "content" + ] + }, + "url": { + "description": "Specifies the location of the provenance file", + "type": "string", + "format": "uri" + }, + "content": { + "description": "Specifies the content of the provenance file inline within the document", + "type": "string", + "format": "byte" + } + }, + "oneOf": [ + { + "required": [ + "url" + ] + }, + { + "required": [ + "content" + ] + } + ] + } + }, + "required": [ + "provenance" + ] + }, + "extraData": { + "description": "Arbitrary content to be included in the verifiable entry in the transparency log", + "type": "object", + "additionalProperties": true + } + }, + "required": [ + "publicKey", + "chart" + ] +} diff --git a/tests/helm.json b/tests/helm.json new file mode 100644 index 000000000..cd21bf64f --- /dev/null +++ b/tests/helm.json @@ -0,0 +1,14 @@ +{ + "kind": "helm", + "apiVersion": "0.0.1", + "spec": { + "publicKey": { + "content": "LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tCgptUUVOQkdEWDdyY0JDQUNkSERBb1ljNFp4cnhhVk5ma1BoSHdOalVES01Ua1FJQy9sYVU2NHlVYVI0WnlIdTlXCjVPSDFkVTYreUNGZXlzeU5wd2EwdjE3MDFRNmdvSmtkNEJMVk5LN0pFS1RwN2dIUHpuU0prZEZzRkFON1A3Z3EKVUIrWkpnVlJyOWc5RjgzZk5KWGZ6UzVEVnhDNjkxeW4xTzVmbTlzR2xYUllzcWhrdHVoT1g4Q0tnUXBkdllOdwphTzhnZmRLMnRCNWVic2s3eUpVdldqMGgxMmZuUWd4UmxCdENpaE44SGYrdG1YVnpLWlpiMjd0TjBTZmRYbWhYCmFTdUhOTVV1blMvaHozeGpjM3JYa2FXWjRsU3FxWk9YMDBDMGhTUklleTRMNnZOQkhpNzVvRnpLRkNlc1pjaEIKVlNHcy8vaVBsbFFNSGlWR0d5WFJ1T3ZvQzFCam5EVDg0V0lkQUJFQkFBRzBERzV2ZEVCeVpXRnNMbU52YllrQgpWQVFUQVFnQVBoWWhCSTFvL1dQMUxjOStxbkpXVzhQSUtLKzJhQndYQlFKZzErNjNBaHNEQlFrRHdtY0FCUXNKCkNBY0NCaFVLQ1FnTEFnUVdBZ01CQWg0QkFoZUFBQW9KRU1QSUtLKzJhQndYbFE4SUFKUHBramlBQ3Q5YWFKSUYKRG41RFlWdTJXRXA5NkZ0OVN3VjRFZ2UyY1BLUXk4SlZ0eXdlU0YrbGdsbDBOMHIvRzN1YWptdWFBR3hHbzl4NAppdWhSZ0dlRFdoZU85VEZLU01MYjBGajdGeVJnMVhPSElYdWpoN1dsbm5ZVm5HM2hnOExZaHpKZW5meU5MQXNPCjZDdEVSMm5hYWRoSk1iS3doTmw2WDg2SUx0akMrTEt5UVBOSzlFQ01WVXdaSFVMekVza29KTHNPRlc2WS9JMkYKcThxb0FTUUs3ZG1NMFpKSEFrd1pUZUpnQXhRdkJRdEhrVEE3THhCWE5ZWGJFNElhTUFVV015TDlybmxrVW41ZQppQXI5VytPQXlVUHdTOFduNXMvYXMwWUc3dW9GdmZBWWp4NURMc0wxTGYzVUJ1MUZYTk52aGczMFdha1RFRFlwCmt5VE8zUUc1QVEwRVlOZnV0d0VJQU1wcGxaeXlIaGNJK013OE1XeUtvdlJxSG9hRVVlRzc1NFhTNWMvWUVpK1QKaDU1ZzRCa1dmMFRxK1paMUMwUUdkTC9oNnV6S2VEL2h3cVJKazZ0U0VkV0VWMTFzNmFnbFM3cHM5WjNrYWR5OAo3RGl3REM3R09QdWVWUnRMZnlqUUNHZnB1b0tyaURFVjE1NVcxM0JPVEdpTWluOWV2V2ZVVGVaNVhGTzVVSTVTCkNIRFk4dS96a2VwVkZIRS92TS9HWDVlbzJOVGtlWldCd3pEdkFkMHBtaEx5Q0t4ZkpkTEFTSUtqU21GMmFQbnUKV0NYVDczMkFqM0pZMnZMNlozazFyRlQzZ2JTY1RGRkwrSHh5dFA1UUFvUjBJd1ZGL1E1Rncyd0tkLzFQSHQ4SQp1d0RkMmVxQnJ0MzJKUFlTdWdtZVpuRVpGRFNJWVh5dWk0akZGaXBmMHZFQUVRRUFBWWtCUEFRWUFRZ0FKaFloCkJJMW8vV1AxTGM5K3FuSldXOFBJS0srMmFCd1hCUUpnMSs2M0Foc01CUWtEd21jQUFBb0pFTVBJS0srMmFCd1gKWTZnSC8ycDZFS0VBMFhubkljU2FlMnVtcTFYejM5SXZRSE9jdlJQZmd5YnRNZDJ1VnFodXAxN09OaE9Ya3pwYQpxeGxURjNkakR6dTRlaXNLNVhwZUMvNWRIbm96SFBpZXkzQXFPdlZwZFBQenZmYjhPOHBBU3UrdW8rZUUwLzlHCmR3dzQwMU9qTUtpM25WZnZMaXRjdHRjY2JzeldvTXBDR3liSnZvS0JoNGJzalNmZWoxT1hvckVKTkFUWGF6bE4KQ1ZVaEJORHo4bUIrZTNEbXovM3Z6UXJ5ZmtzaTBMeFUxWXBYMWYyc1d4TXE4a0xwM3lKV25qUklZSlE5QTdWcwpITXY2NWViS2xmcG9VR0N0V2lLRDJiRjVseXNGb2lMK2dETnpzYmI5eWxXZmxqbVlPRncrd05DRnZmL3lIWXhYCnhmbXJNZFowbmJ2UmZqdU1yMjdlT1kvdk8rTT0KPUlxSHUKLS0tLS1FTkQgUEdQIFBVQkxJQyBLRVkgQkxPQ0stLS0tLQo=" + }, + "chart": { + "provenance": { + "content": "LS0tLS1CRUdJTiBQR1AgU0lHTkVEIE1FU1NBR0UtLS0tLQpIYXNoOiBTSEE1MTIKCmFwaVZlcnNpb246IHYyCmRlc2NyaXB0aW9uOiBUZXN0IEhlbG0gQ2hhcnQKbmFtZTogdGVzdAp0eXBlOiBhcHBsaWNhdGlvbgp2ZXJzaW9uOiAwLjEuMAoKLi4uCmZpbGVzOgogIHRlc3QtMC4xLjAudGd6OiBzaGEyNTY6NmRlYzdlYTIxZTY1NWQ1Nzk2YzFlMjE0Y2ZiNzViNzM0MjhiMmFiZmEyZTY2YzhmN2JjNjRmZjRhN2IzYjI5ZgotLS0tLUJFR0lOIFBHUCBTSUdOQVRVUkUtLS0tLQoKd3NCY0JBRUJDZ0FRQlFKZzEvNzFDUkREeUNpdnRtZ2NGd0FBTUVjSUFCYVJGcUhoYjZpYlFxSUM3eGtuMFU3agpTREVLb0RQdDdsS0psQldlLzlRMWhFMUw3QXNsY3prQ0JCZExJNjdsSEpuRVFINmRvVDJuNnd3M3lvSkp3N2d3Cnc0WmtOcWhkWUFZYllERThGbHdnTWRpM1ZxUHJDQUJaQTJHOXpoOVBjbWpyUzFTK0dyem51aksyNk9MQTNvb2MKZ2lCVExKNXdzVnVQMmFRcHIrYjNjeks0cExtV2pzMWxEeXdvRXRwdHhnSytPb3l0MlhxVHprbVdEWnZTaVlFUQp2cmJiNzFtdVBiSlZuMCsxbVc4OEV5QUNvVWlxTlpMdTE1a3lOZ1NaQzZCeS9DNThSUGF0WXo5RGlleVpvU0lRCmtKZVRYL05lSGZ3QkhicHk3ZmVuRGZuTWJqNExxanZMeGV5b2Q3TmMvNTdkVWVzaW1SKysyWkM1QTRia3ErYz0KPUJOVGsKLS0tLS1FTkQgUEdQIFNJR05BVFVSRS0tLS0t" + } + } + } +} \ No newline at end of file diff --git a/tests/test-0.1.0.tgz b/tests/test-0.1.0.tgz new file mode 100644 index 000000000..e4061d315 Binary files /dev/null and b/tests/test-0.1.0.tgz differ diff --git a/tests/test-0.1.0.tgz.prov b/tests/test-0.1.0.tgz.prov new file mode 100644 index 000000000..002e169d3 --- /dev/null +++ b/tests/test-0.1.0.tgz.prov @@ -0,0 +1,22 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +apiVersion: v2 +description: Test Helm Chart +name: test +type: application +version: 0.1.0 + +... +files: + test-0.1.0.tgz: sha256:6dec7ea21e655d5796c1e214cfb75b73428b2abfa2e66c8f7bc64ff4a7b3b29f +-----BEGIN PGP SIGNATURE----- + +wsBcBAEBCgAQBQJg1/71CRDDyCivtmgcFwAAMEcIABaRFqHhb6ibQqIC7xkn0U7j +SDEKoDPt7lKJlBWe/9Q1hE1L7AslczkCBBdLI67lHJnEQH6doT2n6ww3yoJJw7gw +w4ZkNqhdYAYbYDE8FlwgMdi3VqPrCABZA2G9zh9PcmjrS1S+GrznujK26OLA3ooc +giBTLJ5wsVuP2aQpr+b3czK4pLmWjs1lDywoEtptxgK+Ooyt2XqTzkmWDZvSiYEQ +vrbb71muPbJVn0+1mW88EyACoUiqNZLu15kyNgSZC6By/C58RPatYz9DieyZoSIQ +kJeTX/NeHfwBHbpy7fenDfnMbj4LqjvLxeyod7Nc/57dUesimR++2ZC5A4bkq+c= +=BNTk +-----END PGP SIGNATURE----- \ No newline at end of file diff --git a/tests/test_helm_armor.pub b/tests/test_helm_armor.pub new file mode 100644 index 000000000..535a257c5 --- /dev/null +++ b/tests/test_helm_armor.pub @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBGDX7rcBCACdHDAoYc4ZxrxaVNfkPhHwNjUDKMTkQIC/laU64yUaR4ZyHu9W +5OH1dU6+yCFeysyNpwa0v1701Q6goJkd4BLVNK7JEKTp7gHPznSJkdFsFAN7P7gq +UB+ZJgVRr9g9F83fNJXfzS5DVxC691yn1O5fm9sGlXRYsqhktuhOX8CKgQpdvYNw +aO8gfdK2tB5ebsk7yJUvWj0h12fnQgxRlBtCihN8Hf+tmXVzKZZb27tN0SfdXmhX +aSuHNMUunS/hz3xjc3rXkaWZ4lSqqZOX00C0hSRIey4L6vNBHi75oFzKFCesZchB +VSGs//iPllQMHiVGGyXRuOvoC1BjnDT84WIdABEBAAG0DG5vdEByZWFsLmNvbYkB +VAQTAQgAPhYhBI1o/WP1Lc9+qnJWW8PIKK+2aBwXBQJg1+63AhsDBQkDwmcABQsJ +CAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEMPIKK+2aBwXlQ8IAJPpkjiACt9aaJIF +Dn5DYVu2WEp96Ft9SwV4Ege2cPKQy8JVtyweSF+lgll0N0r/G3uajmuaAGxGo9x4 +iuhRgGeDWheO9TFKSMLb0Fj7FyRg1XOHIXujh7WlnnYVnG3hg8LYhzJenfyNLAsO +6CtER2naadhJMbKwhNl6X86ILtjC+LKyQPNK9ECMVUwZHULzEskoJLsOFW6Y/I2F +q8qoASQK7dmM0ZJHAkwZTeJgAxQvBQtHkTA7LxBXNYXbE4IaMAUWMyL9rnlkUn5e +iAr9W+OAyUPwS8Wn5s/as0YG7uoFvfAYjx5DLsL1Lf3UBu1FXNNvhg30WakTEDYp +kyTO3QG5AQ0EYNfutwEIAMpplZyyHhcI+Mw8MWyKovRqHoaEUeG754XS5c/YEi+T +h55g4BkWf0Tq+ZZ1C0QGdL/h6uzKeD/hwqRJk6tSEdWEV11s6aglS7ps9Z3kady8 +7DiwDC7GOPueVRtLfyjQCGfpuoKriDEV155W13BOTGiMin9evWfUTeZ5XFO5UI5S +CHDY8u/zkepVFHE/vM/GX5eo2NTkeZWBwzDvAd0pmhLyCKxfJdLASIKjSmF2aPnu +WCXT732Aj3JY2vL6Z3k1rFT3gbScTFFL+HxytP5QAoR0IwVF/Q5Fw2wKd/1PHt8I +uwDd2eqBrt32JPYSugmeZnEZFDSIYXyui4jFFipf0vEAEQEAAYkBPAQYAQgAJhYh +BI1o/WP1Lc9+qnJWW8PIKK+2aBwXBQJg1+63AhsMBQkDwmcAAAoJEMPIKK+2aBwX +Y6gH/2p6EKEA0XnnIcSae2umq1Xz39IvQHOcvRPfgybtMd2uVqhup17ONhOXkzpa +qxlTF3djDzu4eisK5XpeC/5dHnozHPiey3AqOvVpdPPzvfb8O8pASu+uo+eE0/9G +dww401OjMKi3nVfvLitcttccbszWoMpCGybJvoKBh4bsjSfej1OXorEJNATXazlN +CVUhBNDz8mB+e3Dmz/3vzQryfksi0LxU1YpX1f2sWxMq8kLp3yJWnjRIYJQ9A7Vs +HMv65ebKlfpoUGCtWiKD2bF5lysFoiL+gDNzsbb9ylWfljmYOFw+wNCFvf/yHYxX +xfmrMdZ0nbvRfjuMr27eOY/vO+M= +=IqHu +-----END PGP PUBLIC KEY BLOCK-----