mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-15 04:42:51 -06:00
3e1af3bd6a
Some work I did during the holidays as part of improving the performance of interpolated strings. This change is however not isolated to those, but updates the `String()` implementation of all AST node types (term values and policy components). This change also lays the groundwork for migrating OPA to the `json/v2` package once that's stable. The `json/v2` package provides low-level functions for zero alloc marshalling via appenders — and well, here they are. The appenders here should be usable for that purpose with only a few tweaks needed for the few cases where our `String()` implementations aren't also valid JSON. Creating perfectly sized buffers requires knowing the expected length beforehand. In order to do this, each component now implements not only `encoding.AppendText` but a new custom `StringLengther` interface, which allows asking any AST node about its `StringLength()` before `make`ing a buffer of that length. We could definitely consider adding these to e.g. the `Value` or `Node` interfaces, but I've left that out of this PR as it's an easy thing to do later should we want to, and I guess there's always some concerns about changing public interfaces even when they're not meant to be implemented by external code. While no `Value` appenders allocate and almost none of the policy appenders do either, one notable exception is `Module` when there are annotations present, as they are a bit of a (YAML) special case. It's doable, but as serializing full modules isn't on a hot path anywhere, I have chosen to defer that work to the future. Signed-off-by: Anders Eknert <anders.eknert@apple.com>
136 lines
3.1 KiB
Go
136 lines
3.1 KiB
Go
// Copyright 2020 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package bundle
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/md5"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/json"
|
|
"fmt"
|
|
"hash"
|
|
"io"
|
|
|
|
"github.com/open-policy-agent/opa/v1/util"
|
|
)
|
|
|
|
// HashingAlgorithm represents a subset of hashing algorithms implemented in Go
|
|
type HashingAlgorithm string
|
|
|
|
// Supported values for HashingAlgorithm
|
|
const (
|
|
MD5 HashingAlgorithm = "MD5"
|
|
SHA1 HashingAlgorithm = "SHA-1"
|
|
SHA224 HashingAlgorithm = "SHA-224"
|
|
SHA256 HashingAlgorithm = "SHA-256"
|
|
SHA384 HashingAlgorithm = "SHA-384"
|
|
SHA512 HashingAlgorithm = "SHA-512"
|
|
SHA512224 HashingAlgorithm = "SHA-512-224"
|
|
SHA512256 HashingAlgorithm = "SHA-512-256"
|
|
)
|
|
|
|
// String returns the string representation of a HashingAlgorithm
|
|
func (alg HashingAlgorithm) String() string {
|
|
return string(alg)
|
|
}
|
|
|
|
// SignatureHasher computes a signature digest for a file with (structured or unstructured) data and policy
|
|
type SignatureHasher interface {
|
|
HashFile(v any) ([]byte, error)
|
|
}
|
|
|
|
type hasher struct {
|
|
h func() hash.Hash // hash function factory
|
|
}
|
|
|
|
// NewSignatureHasher returns a signature hasher suitable for a particular hashing algorithm
|
|
func NewSignatureHasher(alg HashingAlgorithm) (SignatureHasher, error) {
|
|
h := &hasher{}
|
|
|
|
switch alg {
|
|
case MD5:
|
|
h.h = md5.New
|
|
case SHA1:
|
|
h.h = sha1.New
|
|
case SHA224:
|
|
h.h = sha256.New224
|
|
case SHA256:
|
|
h.h = sha256.New
|
|
case SHA384:
|
|
h.h = sha512.New384
|
|
case SHA512:
|
|
h.h = sha512.New
|
|
case SHA512224:
|
|
h.h = sha512.New512_224
|
|
case SHA512256:
|
|
h.h = sha512.New512_256
|
|
default:
|
|
return nil, fmt.Errorf("unsupported hashing algorithm: %s", alg)
|
|
}
|
|
|
|
return h, nil
|
|
}
|
|
|
|
// HashFile hashes the file content, JSON or binary, both in golang native format.
|
|
func (h *hasher) HashFile(v any) ([]byte, error) {
|
|
hf := h.h()
|
|
walk(v, hf)
|
|
return hf.Sum(nil), nil
|
|
}
|
|
|
|
// walk hashes the file content, JSON or binary, both in golang native format.
|
|
//
|
|
// Computation for unstructured documents is a hash of the document.
|
|
//
|
|
// Computation for the types of structured JSON document is as follows:
|
|
//
|
|
// object: Hash {, then each key (in alphabetical order) and digest of the value, then comma (between items) and finally }.
|
|
//
|
|
// array: Hash [, then digest of the value, then comma (between items) and finally ].
|
|
func walk(v any, h io.Writer) {
|
|
|
|
switch x := v.(type) {
|
|
case map[string]any:
|
|
_, _ = h.Write([]byte("{"))
|
|
|
|
for i, key := range util.KeysSorted(x) {
|
|
if i > 0 {
|
|
_, _ = h.Write([]byte(","))
|
|
}
|
|
|
|
_, _ = h.Write(encodePrimitive(key))
|
|
_, _ = h.Write([]byte(":"))
|
|
walk(x[key], h)
|
|
}
|
|
|
|
_, _ = h.Write([]byte("}"))
|
|
case []any:
|
|
_, _ = h.Write([]byte("["))
|
|
|
|
for i, e := range x {
|
|
if i > 0 {
|
|
_, _ = h.Write([]byte(","))
|
|
}
|
|
walk(e, h)
|
|
}
|
|
|
|
_, _ = h.Write([]byte("]"))
|
|
case []byte:
|
|
_, _ = h.Write(x)
|
|
default:
|
|
_, _ = h.Write(encodePrimitive(x))
|
|
}
|
|
}
|
|
|
|
func encodePrimitive(v any) []byte {
|
|
var buf bytes.Buffer
|
|
encoder := json.NewEncoder(&buf)
|
|
encoder.SetEscapeHTML(false)
|
|
_ = encoder.Encode(v)
|
|
return bytes.Trim(buf.Bytes(), "\n")
|
|
}
|