build(deps): bump github.com/open-policy-agent/opa from 1.6.0 to 1.8.0

Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 1.6.0 to 1.8.0.
- [Release notes](https://github.com/open-policy-agent/opa/releases)
- [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-policy-agent/opa/compare/v1.6.0...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/open-policy-agent/opa
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2025-09-23 10:28:55 +02:00
committed by Ralf Haferkamp
parent 98d773bb9b
commit 76ac20e9e8
419 changed files with 57008 additions and 13314 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+13 -5
View File
@@ -120,8 +120,12 @@ func Load(configFile string, overrides []string, overrideFiles []string) ([]byte
// regex looking for ${...} notation strings
var envRegex = regexp.MustCompile(`(?U:\${.*})`)
// subEnvVars will look for any environment variables in the passed in string
// SubEnvVars will look for any environment variables in the passed in string
// with the syntax of ${VAR_NAME} and replace that string with ENV[VAR_NAME]
func SubEnvVars(s string) string {
return subEnvVars(s)
}
func subEnvVars(s string) string {
updatedConfig := envRegex.ReplaceAllStringFunc(s, func(s string) string {
// Trim off the '${' and '}'
@@ -131,10 +135,14 @@ func subEnvVars(s string) string {
}
varName := s[2 : len(s)-1]
// Lookup the variable in the environment. We play by
// bash rules.. if its undefined we'll treat it as an
// empty string instead of raising an error.
return os.Getenv(varName)
// Lookup the variable in the environment. We do not
// play by bash rules: if its undefined we'll keep it
// as-is, it could be replaced somewhere down the line.
// If it's set to "", we'll return that.
if lu, ok := os.LookupEnv(varName); ok {
return lu
}
return s
})
return updatedConfig
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 lestrrat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-112
View File
@@ -1,112 +0,0 @@
// Package buffer provides a very thin wrapper around []byte buffer called
// `Buffer`, to provide functionalities that are often used within the jwx
// related packages
package buffer
import (
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
)
// Buffer wraps `[]byte` and provides functions that are often used in
// the jwx related packages. One notable difference is that while
// encoding/json marshalls `[]byte` using base64.StdEncoding, this
// module uses base64.RawURLEncoding as mandated by the spec
type Buffer []byte
// FromUint creates a `Buffer` from an unsigned int
func FromUint(v uint64) Buffer {
data := make([]byte, 8)
binary.BigEndian.PutUint64(data, v)
i := 0
for ; i < len(data); i++ {
if data[i] != 0x0 {
break
}
}
return Buffer(data[i:])
}
// FromBase64 constructs a new Buffer from a base64 encoded data
func FromBase64(v []byte) (Buffer, error) {
b := Buffer{}
if err := b.Base64Decode(v); err != nil {
return Buffer(nil), fmt.Errorf("failed to decode from base64: %w", err)
}
return b, nil
}
// FromNData constructs a new Buffer from a "n:data" format
// (I made that name up)
func FromNData(v []byte) (Buffer, error) {
size := binary.BigEndian.Uint32(v)
buf := make([]byte, int(size))
copy(buf, v[4:4+size])
return Buffer(buf), nil
}
// Bytes returns the raw bytes that comprises the Buffer
func (b Buffer) Bytes() []byte {
return []byte(b)
}
// NData returns Datalen || Data, where Datalen is a 32 bit counter for
// the length of the following data, and Data is the octets that comprise
// the buffer data
func (b Buffer) NData() []byte {
buf := make([]byte, 4+b.Len())
binary.BigEndian.PutUint32(buf, uint32(b.Len()))
copy(buf[4:], b.Bytes())
return buf
}
// Len returns the number of bytes that the Buffer holds
func (b Buffer) Len() int {
return len(b)
}
// Base64Encode encodes the contents of the Buffer using base64.RawURLEncoding
func (b Buffer) Base64Encode() ([]byte, error) {
enc := base64.RawURLEncoding
out := make([]byte, enc.EncodedLen(len(b)))
enc.Encode(out, b)
return out, nil
}
// Base64Decode decodes the contents of the Buffer using base64.RawURLEncoding
func (b *Buffer) Base64Decode(v []byte) error {
enc := base64.RawURLEncoding
out := make([]byte, enc.DecodedLen(len(v)))
n, err := enc.Decode(out, v)
if err != nil {
return fmt.Errorf("failed to decode from base64: %w", err)
}
out = out[:n]
*b = Buffer(out)
return nil
}
// MarshalJSON marshals the buffer into JSON format after encoding the buffer
// with base64.RawURLEncoding
func (b Buffer) MarshalJSON() ([]byte, error) {
v, err := b.Base64Encode()
if err != nil {
return nil, fmt.Errorf("failed to encode to base64: %w", err)
}
return json.Marshal(string(v))
}
// UnmarshalJSON unmarshals from a JSON string into a Buffer, after decoding it
// with base64.RawURLEncoding
func (b *Buffer) UnmarshalJSON(data []byte) error {
var x string
if err := json.Unmarshal(data, &x); err != nil {
return fmt.Errorf("failed to unmarshal JSON: %w", err)
}
return b.Base64Decode([]byte(x))
}
-11
View File
@@ -1,11 +0,0 @@
package jwa
// EllipticCurveAlgorithm represents the algorithms used for EC keys
type EllipticCurveAlgorithm string
// Supported values for EllipticCurveAlgorithm
const (
P256 EllipticCurveAlgorithm = "P-256"
P384 EllipticCurveAlgorithm = "P-384"
P521 EllipticCurveAlgorithm = "P-521"
)
-67
View File
@@ -1,67 +0,0 @@
package jwa
import (
"errors"
"fmt"
"strconv"
)
// KeyType represents the key type ("kty") that are supported
type KeyType string
var keyTypeAlg = map[string]struct{}{"EC": {}, "oct": {}, "RSA": {}}
// Supported values for KeyType
const (
EC KeyType = "EC" // Elliptic Curve
InvalidKeyType KeyType = "" // Invalid KeyType
OctetSeq KeyType = "oct" // Octet sequence (used to represent symmetric keys)
RSA KeyType = "RSA" // RSA
)
// Accept is used when conversion from values given by
// outside sources (such as JSON payloads) is required
func (keyType *KeyType) Accept(value any) error {
var tmp KeyType
switch x := value.(type) {
case string:
tmp = KeyType(x)
case KeyType:
tmp = x
default:
return fmt.Errorf("invalid type for jwa.KeyType: %T", value)
}
_, ok := keyTypeAlg[tmp.String()]
if !ok {
return errors.New("unknown Key Type algorithm")
}
*keyType = tmp
return nil
}
// String returns the string representation of a KeyType
func (keyType KeyType) String() string {
return string(keyType)
}
// UnmarshalJSON unmarshals and checks data as KeyType Algorithm
func (keyType *KeyType) UnmarshalJSON(data []byte) error {
var quote byte = '"'
var quoted string
if data[0] == quote {
var err error
quoted, err = strconv.Unquote(string(data))
if err != nil {
return fmt.Errorf("failed to process signature algorithm: %w", err)
}
} else {
quoted = string(data)
}
_, ok := keyTypeAlg[quoted]
if !ok {
return errors.New("unknown signature algorithm")
}
*keyType = KeyType(quoted)
return nil
}
-29
View File
@@ -1,29 +0,0 @@
package jwa
import (
"crypto/elliptic"
"github.com/open-policy-agent/opa/internal/jwx/buffer"
)
// EllipticCurve provides a indirect type to standard elliptic curve such that we can
// use it for unmarshal
type EllipticCurve struct {
elliptic.Curve
}
// AlgorithmParameters provides a single structure suitable to unmarshaling any JWK
type AlgorithmParameters struct {
N buffer.Buffer `json:"n,omitempty"`
E buffer.Buffer `json:"e,omitempty"`
D buffer.Buffer `json:"d,omitempty"`
P buffer.Buffer `json:"p,omitempty"`
Q buffer.Buffer `json:"q,omitempty"`
Dp buffer.Buffer `json:"dp,omitempty"`
Dq buffer.Buffer `json:"dq,omitempty"`
Qi buffer.Buffer `json:"qi,omitempty"`
Crv EllipticCurveAlgorithm `json:"crv,omitempty"`
X buffer.Buffer `json:"x,omitempty"`
Y buffer.Buffer `json:"y,omitempty"`
K buffer.Buffer `json:"k,omitempty"`
}
-78
View File
@@ -1,78 +0,0 @@
package jwa
import (
"errors"
"fmt"
"strconv"
)
// SignatureAlgorithm represents the various signature algorithms as described in https://tools.ietf.org/html/rfc7518#section-3.1
type SignatureAlgorithm string
var signatureAlg = map[string]struct{}{"ES256": {}, "ES384": {}, "ES512": {}, "HS256": {}, "HS384": {}, "HS512": {}, "PS256": {}, "PS384": {}, "PS512": {}, "RS256": {}, "RS384": {}, "RS512": {}, "none": {}}
// Supported values for SignatureAlgorithm
const (
ES256 SignatureAlgorithm = "ES256" // ECDSA using P-256 and SHA-256
ES384 SignatureAlgorithm = "ES384" // ECDSA using P-384 and SHA-384
ES512 SignatureAlgorithm = "ES512" // ECDSA using P-521 and SHA-512
HS256 SignatureAlgorithm = "HS256" // HMAC using SHA-256
HS384 SignatureAlgorithm = "HS384" // HMAC using SHA-384
HS512 SignatureAlgorithm = "HS512" // HMAC using SHA-512
NoSignature SignatureAlgorithm = "none"
PS256 SignatureAlgorithm = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
PS384 SignatureAlgorithm = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
PS512 SignatureAlgorithm = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
RS256 SignatureAlgorithm = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
RS384 SignatureAlgorithm = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
RS512 SignatureAlgorithm = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
NoValue SignatureAlgorithm = "" // No value is different from none
Unsupported SignatureAlgorithm = "unsupported"
)
// Accept is used when conversion from values given by
// outside sources (such as JSON payloads) is required
func (signature *SignatureAlgorithm) Accept(value any) error {
var tmp SignatureAlgorithm
switch x := value.(type) {
case string:
tmp = SignatureAlgorithm(x)
case SignatureAlgorithm:
tmp = x
default:
return fmt.Errorf("invalid type for jwa.SignatureAlgorithm: %T", value)
}
_, ok := signatureAlg[tmp.String()]
if !ok {
return errors.New("unknown signature algorithm")
}
*signature = tmp
return nil
}
// String returns the string representation of a SignatureAlgorithm
func (signature SignatureAlgorithm) String() string {
return string(signature)
}
// UnmarshalJSON unmarshals and checks data as Signature Algorithm
func (signature *SignatureAlgorithm) UnmarshalJSON(data []byte) error {
var quote byte = '"'
var quoted string
if data[0] == quote {
var err error
quoted, err = strconv.Unquote(string(data))
if err != nil {
return fmt.Errorf("failed to process signature algorithm: %w", err)
}
} else {
quoted = string(data)
}
_, ok := signatureAlg[quoted]
if !ok {
*signature = Unsupported
return nil
}
*signature = SignatureAlgorithm(quoted)
return nil
}
-120
View File
@@ -1,120 +0,0 @@
package jwk
import (
"crypto/ecdsa"
"crypto/elliptic"
"errors"
"fmt"
"math/big"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
func newECDSAPublicKey(key *ecdsa.PublicKey) (*ECDSAPublicKey, error) {
var hdr StandardHeaders
err := hdr.Set(KeyTypeKey, jwa.EC)
if err != nil {
return nil, fmt.Errorf("failed to set Key Type: %w", err)
}
return &ECDSAPublicKey{
StandardHeaders: &hdr,
key: key,
}, nil
}
func newECDSAPrivateKey(key *ecdsa.PrivateKey) (*ECDSAPrivateKey, error) {
var hdr StandardHeaders
err := hdr.Set(KeyTypeKey, jwa.EC)
if err != nil {
return nil, fmt.Errorf("failed to set Key Type: %w", err)
}
return &ECDSAPrivateKey{
StandardHeaders: &hdr,
key: key,
}, nil
}
// Materialize returns the EC-DSA public key represented by this JWK
func (k ECDSAPublicKey) Materialize() (any, error) {
return k.key, nil
}
// Materialize returns the EC-DSA private key represented by this JWK
func (k ECDSAPrivateKey) Materialize() (any, error) {
return k.key, nil
}
// GenerateKey creates a ECDSAPublicKey from JWK format
func (k *ECDSAPublicKey) GenerateKey(keyJSON *RawKeyJSON) error {
var x, y big.Int
if keyJSON.X == nil || keyJSON.Y == nil || keyJSON.Crv == "" {
return errors.New("missing mandatory key parameters X, Y or Crv")
}
x.SetBytes(keyJSON.X.Bytes())
y.SetBytes(keyJSON.Y.Bytes())
var curve elliptic.Curve
switch keyJSON.Crv {
case jwa.P256:
curve = elliptic.P256()
case jwa.P384:
curve = elliptic.P384()
case jwa.P521:
curve = elliptic.P521()
default:
return fmt.Errorf("invalid curve name %s", keyJSON.Crv)
}
*k = ECDSAPublicKey{
StandardHeaders: &keyJSON.StandardHeaders,
key: &ecdsa.PublicKey{
Curve: curve,
X: &x,
Y: &y,
},
}
return nil
}
// GenerateKey creates a ECDSAPrivateKey from JWK format
func (k *ECDSAPrivateKey) GenerateKey(keyJSON *RawKeyJSON) error {
if keyJSON.D == nil {
return errors.New("missing mandatory key parameter D")
}
eCDSAPublicKey := &ECDSAPublicKey{}
err := eCDSAPublicKey.GenerateKey(keyJSON)
if err != nil {
return fmt.Errorf("failed to generate public key: %w", err)
}
dBytes := keyJSON.D.Bytes()
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
// octets (where n is the order of the curve). This is because the private
// key d must be in the interval [1, n-1] so the bitlength of d should be
// no larger than the bitlength of n-1. The easiest way to find the octet
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
// bit sequence right by 3, which is essentially dividing by 8 and adding
// 1 if there is any remainder. Thus, the private key value d should be
// output to (bitlength(n-1)+7)>>3 octets.
n := eCDSAPublicKey.key.Params().N
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
if octetLength-len(dBytes) != 0 {
return errors.New("failed to generate private key. Incorrect D value")
}
privateKey := &ecdsa.PrivateKey{
PublicKey: *eCDSAPublicKey.key,
D: (&big.Int{}).SetBytes(keyJSON.D.Bytes()),
}
k.key = privateKey
k.StandardHeaders = &keyJSON.StandardHeaders
return nil
}
-178
View File
@@ -1,178 +0,0 @@
package jwk
import (
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
// Convenience constants for common JWK parameters
const (
AlgorithmKey = "alg"
KeyIDKey = "kid"
KeyOpsKey = "key_ops"
KeyTypeKey = "kty"
KeyUsageKey = "use"
PrivateParamsKey = "privateParams"
)
// Headers provides a common interface to all future possible headers
type Headers interface {
Get(string) (any, bool)
Set(string, any) error
Walk(func(string, any) error) error
GetAlgorithm() jwa.SignatureAlgorithm
GetKeyID() string
GetKeyOps() KeyOperationList
GetKeyType() jwa.KeyType
GetKeyUsage() string
GetPrivateParams() map[string]any
}
// StandardHeaders stores the common JWK parameters
type StandardHeaders struct {
Algorithm *jwa.SignatureAlgorithm `json:"alg,omitempty"` // https://tools.ietf.org/html/rfc7517#section-4.4
KeyID string `json:"kid,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.4
KeyOps KeyOperationList `json:"key_ops,omitempty"` // https://tools.ietf.org/html/rfc7517#section-4.3
KeyType jwa.KeyType `json:"kty,omitempty"` // https://tools.ietf.org/html/rfc7517#section-4.1
KeyUsage string `json:"use,omitempty"` // https://tools.ietf.org/html/rfc7517#section-4.2
PrivateParams map[string]any `json:"privateParams,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.4
}
// GetAlgorithm is a convenience function to retrieve the corresponding value stored in the StandardHeaders
func (h *StandardHeaders) GetAlgorithm() jwa.SignatureAlgorithm {
if v := h.Algorithm; v != nil {
return *v
}
return jwa.NoValue
}
// GetKeyID is a convenience function to retrieve the corresponding value stored in the StandardHeaders
func (h *StandardHeaders) GetKeyID() string {
return h.KeyID
}
// GetKeyOps is a convenience function to retrieve the corresponding value stored in the StandardHeaders
func (h *StandardHeaders) GetKeyOps() KeyOperationList {
return h.KeyOps
}
// GetKeyType is a convenience function to retrieve the corresponding value stored in the StandardHeaders
func (h *StandardHeaders) GetKeyType() jwa.KeyType {
return h.KeyType
}
// GetKeyUsage is a convenience function to retrieve the corresponding value stored in the StandardHeaders
func (h *StandardHeaders) GetKeyUsage() string {
return h.KeyUsage
}
// GetPrivateParams is a convenience function to retrieve the corresponding value stored in the StandardHeaders
func (h *StandardHeaders) GetPrivateParams() map[string]any {
return h.PrivateParams
}
// Get is a general getter function for JWK StandardHeaders structure
func (h *StandardHeaders) Get(name string) (any, bool) {
switch name {
case AlgorithmKey:
alg := h.GetAlgorithm()
if alg != jwa.NoValue {
return alg, true
}
return nil, false
case KeyIDKey:
v := h.KeyID
if v == "" {
return nil, false
}
return v, true
case KeyOpsKey:
v := h.KeyOps
if v == nil {
return nil, false
}
return v, true
case KeyTypeKey:
v := h.KeyType
if v == jwa.InvalidKeyType {
return nil, false
}
return v, true
case KeyUsageKey:
v := h.KeyUsage
if v == "" {
return nil, false
}
return v, true
case PrivateParamsKey:
v := h.PrivateParams
if len(v) == 0 {
return nil, false
}
return v, true
default:
return nil, false
}
}
// Set is a general getter function for JWK StandardHeaders structure
func (h *StandardHeaders) Set(name string, value any) error {
switch name {
case AlgorithmKey:
var acceptor jwa.SignatureAlgorithm
if err := acceptor.Accept(value); err != nil {
return fmt.Errorf("invalid value for %s key: %w", AlgorithmKey, err)
}
h.Algorithm = &acceptor
return nil
case KeyIDKey:
if v, ok := value.(string); ok {
h.KeyID = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", KeyIDKey, value)
case KeyOpsKey:
if err := h.KeyOps.Accept(value); err != nil {
return fmt.Errorf("invalid value for %s key: %w", KeyOpsKey, err)
}
return nil
case KeyTypeKey:
if err := h.KeyType.Accept(value); err != nil {
return fmt.Errorf("invalid value for %s key: %w", KeyTypeKey, err)
}
return nil
case KeyUsageKey:
if v, ok := value.(string); ok {
h.KeyUsage = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", KeyUsageKey, value)
case PrivateParamsKey:
if v, ok := value.(map[string]any); ok {
h.PrivateParams = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", PrivateParamsKey, value)
default:
return fmt.Errorf("invalid key: %s", name)
}
}
// Walk iterates over all JWK standard headers fields while applying a function to its value.
func (h StandardHeaders) Walk(f func(string, any) error) error {
for _, key := range []string{AlgorithmKey, KeyIDKey, KeyOpsKey, KeyTypeKey, KeyUsageKey, PrivateParamsKey} {
if v, ok := h.Get(key); ok {
if err := f(key, v); err != nil {
return fmt.Errorf("walk function returned error for %s: %w", key, err)
}
}
}
for k, v := range h.PrivateParams {
if err := f(k, v); err != nil {
return fmt.Errorf("walk function returned error for %s: %w", k, err)
}
}
return nil
}
-71
View File
@@ -1,71 +0,0 @@
package jwk
import (
"crypto/ecdsa"
"crypto/rsa"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
// Set is a convenience struct to allow generating and parsing
// JWK sets as opposed to single JWKs
type Set struct {
Keys []Key `json:"keys"`
}
// Key defines the minimal interface for each of the
// key types. Their use and implementation differ significantly
// between each key types, so you should use type assertions
// to perform more specific tasks with each key
type Key interface {
Headers
// Materialize creates the corresponding key. For example,
// RSA types would create *rsa.PublicKey or *rsa.PrivateKey,
// EC types would create *ecdsa.PublicKey or *ecdsa.PrivateKey,
// and OctetSeq types create a []byte key.
Materialize() (any, error)
GenerateKey(*RawKeyJSON) error
}
// RawKeyJSON is generic type that represents any kind JWK
type RawKeyJSON struct {
StandardHeaders
jwa.AlgorithmParameters
}
// RawKeySetJSON is generic type that represents a JWK Set
type RawKeySetJSON struct {
Keys []RawKeyJSON `json:"keys"`
}
// RSAPublicKey is a type of JWK generated from RSA public keys
type RSAPublicKey struct {
*StandardHeaders
key *rsa.PublicKey
}
// RSAPrivateKey is a type of JWK generated from RSA private keys
type RSAPrivateKey struct {
*StandardHeaders
*jwa.AlgorithmParameters
key *rsa.PrivateKey
}
// SymmetricKey is a type of JWK generated from symmetric keys
type SymmetricKey struct {
*StandardHeaders
key []byte
}
// ECDSAPublicKey is a type of JWK generated from ECDSA public keys
type ECDSAPublicKey struct {
*StandardHeaders
key *ecdsa.PublicKey
}
// ECDSAPrivateKey is a type of JWK generated from ECDH-ES private keys
type ECDSAPrivateKey struct {
*StandardHeaders
key *ecdsa.PrivateKey
}
-153
View File
@@ -1,153 +0,0 @@
// Package jwk implements JWK as described in https://tools.ietf.org/html/rfc7517
package jwk
import (
"crypto/ecdsa"
"crypto/rsa"
"encoding/json"
"errors"
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
// GetPublicKey returns the public key based on the private key type.
// For rsa key types *rsa.PublicKey is returned; for ecdsa key types *ecdsa.PublicKey;
// for byte slice (raw) keys, the key itself is returned. If the corresponding
// public key cannot be deduced, an error is returned
func GetPublicKey(key any) (any, error) {
if key == nil {
return nil, errors.New("jwk.New requires a non-nil key")
}
switch v := key.(type) {
// Mental note: although Public() is defined in both types,
// you can not coalesce the clauses for rsa.PrivateKey and
// ecdsa.PrivateKey, as then `v` becomes any
// b/c the compiler cannot deduce the exact type.
case *rsa.PrivateKey:
return v.Public(), nil
case *ecdsa.PrivateKey:
return v.Public(), nil
case []byte:
return v, nil
default:
return nil, fmt.Errorf("invalid key type %T", key)
}
}
// GetKeyTypeFromKey creates a jwk.Key from the given key.
func GetKeyTypeFromKey(key any) jwa.KeyType {
switch key.(type) {
case *rsa.PrivateKey, *rsa.PublicKey:
return jwa.RSA
case *ecdsa.PrivateKey, *ecdsa.PublicKey:
return jwa.EC
case []byte:
return jwa.OctetSeq
default:
return jwa.InvalidKeyType
}
}
// New creates a jwk.Key from the given key.
func New(key any) (Key, error) {
if key == nil {
return nil, errors.New("jwk.New requires a non-nil key")
}
switch v := key.(type) {
case *rsa.PrivateKey:
return newRSAPrivateKey(v)
case *rsa.PublicKey:
return newRSAPublicKey(v)
case *ecdsa.PrivateKey:
return newECDSAPrivateKey(v)
case *ecdsa.PublicKey:
return newECDSAPublicKey(v)
case []byte:
return newSymmetricKey(v)
default:
return nil, fmt.Errorf("invalid key type %T", key)
}
}
func parse(jwkSrc string) (*Set, error) {
var jwkKeySet Set
var jwkKey Key
rawKeySetJSON := &RawKeySetJSON{}
err := json.Unmarshal([]byte(jwkSrc), rawKeySetJSON)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal JWK Set: %w", err)
}
if len(rawKeySetJSON.Keys) == 0 {
// It might be a single key
rawKeyJSON := &RawKeyJSON{}
err := json.Unmarshal([]byte(jwkSrc), rawKeyJSON)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal JWK: %w", err)
}
jwkKey, err = rawKeyJSON.GenerateKey()
if err != nil {
return nil, fmt.Errorf("failed to generate key: %w", err)
}
// Add to set
jwkKeySet.Keys = append(jwkKeySet.Keys, jwkKey)
} else {
for i := range rawKeySetJSON.Keys {
rawKeyJSON := rawKeySetJSON.Keys[i]
if rawKeyJSON.Algorithm != nil && *rawKeyJSON.Algorithm == jwa.Unsupported {
continue
}
jwkKey, err = rawKeyJSON.GenerateKey()
if err != nil {
return nil, fmt.Errorf("failed to generate key: %w", err)
}
jwkKeySet.Keys = append(jwkKeySet.Keys, jwkKey)
}
}
return &jwkKeySet, nil
}
// ParseBytes parses JWK from the incoming byte buffer.
func ParseBytes(buf []byte) (*Set, error) {
return parse(string(buf))
}
// ParseString parses JWK from the incoming string.
func ParseString(s string) (*Set, error) {
return parse(s)
}
// GenerateKey creates an internal representation of a key from a raw JWK JSON
func (r *RawKeyJSON) GenerateKey() (Key, error) {
var key Key
switch r.KeyType {
case jwa.RSA:
if r.D != nil {
key = &RSAPrivateKey{}
} else {
key = &RSAPublicKey{}
}
case jwa.EC:
if r.D != nil {
key = &ECDSAPrivateKey{}
} else {
key = &ECDSAPublicKey{}
}
case jwa.OctetSeq:
key = &SymmetricKey{}
default:
return nil, errors.New("unrecognized key type")
}
err := key.GenerateKey(r)
if err != nil {
return nil, fmt.Errorf("failed to generate key from JWK: %w", err)
}
return key, nil
}
-67
View File
@@ -1,67 +0,0 @@
package jwk
import (
"encoding/json"
"errors"
"fmt"
)
// KeyUsageType is used to denote what this key should be used for
type KeyUsageType string
const (
// ForSignature is the value used in the headers to indicate that
// this key should be used for signatures
ForSignature KeyUsageType = "sig"
// ForEncryption is the value used in the headers to indicate that
// this key should be used for encryptiong
ForEncryption KeyUsageType = "enc"
)
// KeyOperation is used to denote the allowed operations for a Key
type KeyOperation string
// KeyOperationList represents an slice of KeyOperation
type KeyOperationList []KeyOperation
var keyOps = map[string]struct{}{"sign": {}, "verify": {}, "encrypt": {}, "decrypt": {}, "wrapKey": {}, "unwrapKey": {}, "deriveKey": {}, "deriveBits": {}}
// KeyOperation constants
const (
KeyOpSign KeyOperation = "sign" // (compute digital signature or MAC)
KeyOpVerify KeyOperation = "verify" // (verify digital signature or MAC)
KeyOpEncrypt KeyOperation = "encrypt" // (encrypt content)
KeyOpDecrypt KeyOperation = "decrypt" // (decrypt content and validate decryption, if applicable)
KeyOpWrapKey KeyOperation = "wrapKey" // (encrypt key)
KeyOpUnwrapKey KeyOperation = "unwrapKey" // (decrypt key and validate decryption, if applicable)
KeyOpDeriveKey KeyOperation = "deriveKey" // (derive key)
KeyOpDeriveBits KeyOperation = "deriveBits" // (derive bits not to be used as a key)
)
// Accept determines if Key Operation is valid
func (keyOperationList *KeyOperationList) Accept(v any) error {
switch x := v.(type) {
case KeyOperationList:
*keyOperationList = x
return nil
default:
return fmt.Errorf(`invalid value %T`, v)
}
}
// UnmarshalJSON unmarshals and checks data as KeyType Algorithm
func (keyOperationList *KeyOperationList) UnmarshalJSON(data []byte) error {
var tempKeyOperationList []string
err := json.Unmarshal(data, &tempKeyOperationList)
if err != nil {
return errors.New("invalid key operation")
}
for _, value := range tempKeyOperationList {
_, ok := keyOps[value]
if !ok {
return errors.New("unknown key operation")
}
*keyOperationList = append(*keyOperationList, KeyOperation(value))
}
return nil
}
-133
View File
@@ -1,133 +0,0 @@
package jwk
import (
"crypto/rsa"
"encoding/binary"
"errors"
"fmt"
"math/big"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
func newRSAPublicKey(key *rsa.PublicKey) (*RSAPublicKey, error) {
var hdr StandardHeaders
err := hdr.Set(KeyTypeKey, jwa.RSA)
if err != nil {
return nil, fmt.Errorf("failed to set Key Type: %w", err)
}
return &RSAPublicKey{
StandardHeaders: &hdr,
key: key,
}, nil
}
func newRSAPrivateKey(key *rsa.PrivateKey) (*RSAPrivateKey, error) {
var hdr StandardHeaders
err := hdr.Set(KeyTypeKey, jwa.RSA)
if err != nil {
return nil, fmt.Errorf("failed to set Key Type: %w", err)
}
var algoParams jwa.AlgorithmParameters
// it is needed to use raw encoding to omit the "=" paddings at the end
algoParams.D = key.D.Bytes()
algoParams.P = key.Primes[0].Bytes()
algoParams.Q = key.Primes[1].Bytes()
algoParams.Dp = key.Precomputed.Dp.Bytes()
algoParams.Dq = key.Precomputed.Dq.Bytes()
algoParams.Qi = key.Precomputed.Qinv.Bytes()
// "modulus" (N) from the public key in the private key
algoParams.N = key.PublicKey.N.Bytes()
// make the E a.k.a "coprime"
// https://en.wikipedia.org/wiki/RSA_(cryptosystem)
coprime := make([]byte, 8)
binary.BigEndian.PutUint64(coprime, uint64(key.PublicKey.E))
// find the 1st index of non 0x0 paddings from the beginning
i := 0
for ; i < len(coprime); i++ {
if coprime[i] != 0x0 {
break
}
}
algoParams.E = coprime[i:]
return &RSAPrivateKey{
StandardHeaders: &hdr,
AlgorithmParameters: &algoParams,
key: key,
}, nil
}
// Materialize returns the standard RSA Public Key representation stored in the internal representation
func (k *RSAPublicKey) Materialize() (any, error) {
if k.key == nil {
return nil, errors.New("key has no rsa.PublicKey associated with it")
}
return k.key, nil
}
// Materialize returns the standard RSA Private Key representation stored in the internal representation
func (k *RSAPrivateKey) Materialize() (any, error) {
if k.key == nil {
return nil, errors.New("key has no rsa.PrivateKey associated with it")
}
return k.key, nil
}
// GenerateKey creates a RSAPublicKey from a RawKeyJSON
func (k *RSAPublicKey) GenerateKey(keyJSON *RawKeyJSON) error {
if keyJSON.N == nil || keyJSON.E == nil {
return errors.New("missing mandatory key parameters N or E")
}
rsaPublicKey := &rsa.PublicKey{
N: (&big.Int{}).SetBytes(keyJSON.N.Bytes()),
E: int((&big.Int{}).SetBytes(keyJSON.E.Bytes()).Int64()),
}
k.key = rsaPublicKey
k.StandardHeaders = &keyJSON.StandardHeaders
return nil
}
// GenerateKey creates a RSAPublicKey from a RawKeyJSON
func (k *RSAPrivateKey) GenerateKey(keyJSON *RawKeyJSON) error {
rsaPublicKey := &RSAPublicKey{}
err := rsaPublicKey.GenerateKey(keyJSON)
if err != nil {
return fmt.Errorf("failed to generate public key: %w", err)
}
if keyJSON.D == nil || keyJSON.P == nil || keyJSON.Q == nil {
return errors.New("missing mandatory key parameters D, P or Q")
}
privateKey := &rsa.PrivateKey{
PublicKey: *rsaPublicKey.key,
D: (&big.Int{}).SetBytes(keyJSON.D.Bytes()),
Primes: []*big.Int{
(&big.Int{}).SetBytes(keyJSON.P.Bytes()),
(&big.Int{}).SetBytes(keyJSON.Q.Bytes()),
},
}
if keyJSON.Dp.Len() > 0 {
privateKey.Precomputed.Dp = (&big.Int{}).SetBytes(keyJSON.Dp.Bytes())
}
if keyJSON.Dq.Len() > 0 {
privateKey.Precomputed.Dq = (&big.Int{}).SetBytes(keyJSON.Dq.Bytes())
}
if keyJSON.Qi.Len() > 0 {
privateKey.Precomputed.Qinv = (&big.Int{}).SetBytes(keyJSON.Qi.Bytes())
}
k.key = privateKey
k.StandardHeaders = &keyJSON.StandardHeaders
k.AlgorithmParameters = &keyJSON.AlgorithmParameters
return nil
}
-41
View File
@@ -1,41 +0,0 @@
package jwk
import (
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
func newSymmetricKey(key []byte) (*SymmetricKey, error) {
var hdr StandardHeaders
err := hdr.Set(KeyTypeKey, jwa.OctetSeq)
if err != nil {
return nil, fmt.Errorf("failed to set Key Type: %w", err)
}
return &SymmetricKey{
StandardHeaders: &hdr,
key: key,
}, nil
}
// Materialize returns the octets for this symmetric key.
// Since this is a symmetric key, this just calls Octets
func (s SymmetricKey) Materialize() (any, error) {
return s.Octets(), nil
}
// Octets returns the octets in the key
func (s SymmetricKey) Octets() []byte {
return s.key
}
// GenerateKey creates a Symmetric key from a RawKeyJSON
func (s *SymmetricKey) GenerateKey(keyJSON *RawKeyJSON) error {
*s = SymmetricKey{
StandardHeaders: &keyJSON.StandardHeaders,
key: keyJSON.K,
}
return nil
}
-154
View File
@@ -1,154 +0,0 @@
package jws
import (
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
// Constants for JWS Common parameters
const (
AlgorithmKey = "alg"
ContentTypeKey = "cty"
CriticalKey = "crit"
JWKKey = "jwk"
JWKSetURLKey = "jku"
KeyIDKey = "kid"
PrivateParamsKey = "privateParams"
TypeKey = "typ"
)
// Headers provides a common interface for common header parameters
type Headers interface {
Get(string) (any, bool)
Set(string, any) error
GetAlgorithm() jwa.SignatureAlgorithm
}
// StandardHeaders contains JWS common parameters.
type StandardHeaders struct {
Algorithm jwa.SignatureAlgorithm `json:"alg,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.1
ContentType string `json:"cty,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.10
Critical []string `json:"crit,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.11
JWK string `json:"jwk,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.3
JWKSetURL string `json:"jku,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.2
KeyID string `json:"kid,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.4
PrivateParams map[string]any `json:"privateParams,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.9
Type string `json:"typ,omitempty"` // https://tools.ietf.org/html/rfc7515#section-4.1.9
}
// GetAlgorithm returns algorithm
func (h *StandardHeaders) GetAlgorithm() jwa.SignatureAlgorithm {
return h.Algorithm
}
// Get is a general getter function for StandardHeaders structure
func (h *StandardHeaders) Get(name string) (any, bool) {
switch name {
case AlgorithmKey:
v := h.Algorithm
if v == "" {
return nil, false
}
return v, true
case ContentTypeKey:
v := h.ContentType
if v == "" {
return nil, false
}
return v, true
case CriticalKey:
v := h.Critical
if len(v) == 0 {
return nil, false
}
return v, true
case JWKKey:
v := h.JWK
if v == "" {
return nil, false
}
return v, true
case JWKSetURLKey:
v := h.JWKSetURL
if v == "" {
return nil, false
}
return v, true
case KeyIDKey:
v := h.KeyID
if v == "" {
return nil, false
}
return v, true
case PrivateParamsKey:
v := h.PrivateParams
if len(v) == 0 {
return nil, false
}
return v, true
case TypeKey:
v := h.Type
if v == "" {
return nil, false
}
return v, true
default:
return nil, false
}
}
// Set is a general setter function for StandardHeaders structure
func (h *StandardHeaders) Set(name string, value any) error {
switch name {
case AlgorithmKey:
if err := h.Algorithm.Accept(value); err != nil {
return fmt.Errorf("invalid value for %s key: %w", AlgorithmKey, err)
}
return nil
case ContentTypeKey:
if v, ok := value.(string); ok {
h.ContentType = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", ContentTypeKey, value)
case CriticalKey:
if v, ok := value.([]string); ok {
h.Critical = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", CriticalKey, value)
case JWKKey:
if v, ok := value.(string); ok {
h.JWK = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", JWKKey, value)
case JWKSetURLKey:
if v, ok := value.(string); ok {
h.JWKSetURL = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", JWKSetURLKey, value)
case KeyIDKey:
if v, ok := value.(string); ok {
h.KeyID = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", KeyIDKey, value)
case PrivateParamsKey:
if v, ok := value.(map[string]any); ok {
h.PrivateParams = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", PrivateParamsKey, value)
case TypeKey:
if v, ok := value.(string); ok {
h.Type = v
return nil
}
return fmt.Errorf("invalid value for %s key: %T", TypeKey, value)
default:
return fmt.Errorf("invalid key: %s", name)
}
}
-22
View File
@@ -1,22 +0,0 @@
package jws
// Message represents a full JWS encoded message. Flattened serialization
// is not supported as a struct, but rather it's represented as a
// Message struct with only one `Signature` element.
//
// Do not expect to use the Message object to verify or construct a
// signed payloads with. You should only use this when you want to actually
// want to programmatically view the contents for the full JWS Payload.
//
// To sign and verify, use the appropriate `SignWithOption()` nad `Verify()` functions
type Message struct {
Payload []byte `json:"payload"`
Signatures []*Signature `json:"signatures,omitempty"`
}
// Signature represents the headers and signature of a JWS message
type Signature struct {
Headers Headers `json:"header,omitempty"` // Unprotected Headers
Protected Headers `json:"Protected,omitempty"` // Protected Headers
Signature []byte `json:"signature,omitempty"` // GetSignature
}
-220
View File
@@ -1,220 +0,0 @@
// Package jws implements the digital Signature on JSON based data
// structures as described in https://tools.ietf.org/html/rfc7515
//
// If you do not care about the details, the only things that you
// would need to use are the following functions:
//
// jws.SignWithOption(Payload, algorithm, key)
// jws.Verify(encodedjws, algorithm, key)
//
// To sign, simply use `jws.SignWithOption`. `Payload` is a []byte buffer that
// contains whatever data you want to sign. `alg` is one of the
// jwa.SignatureAlgorithm constants from package jwa. For RSA and
// ECDSA family of algorithms, you will need to prepare a private key.
// For HMAC family, you just need a []byte value. The `jws.SignWithOption`
// function will return the encoded JWS message on success.
//
// To verify, use `jws.Verify`. It will parse the `encodedjws` buffer
// and verify the result using `algorithm` and `key`. Upon successful
// verification, the original Payload is returned, so you can work on it.
package jws
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
"github.com/open-policy-agent/opa/internal/jwx/jwk"
"github.com/open-policy-agent/opa/internal/jwx/jws/sign"
"github.com/open-policy-agent/opa/internal/jwx/jws/verify"
)
// SignLiteral generates a Signature for the given Payload and Headers, and serializes
// it in compact serialization format. In this format you may NOT use
// multiple signers.
func SignLiteral(payload []byte, alg jwa.SignatureAlgorithm, key any, hdrBuf []byte, rnd io.Reader) ([]byte, error) {
encodedHdr := base64.RawURLEncoding.EncodeToString(hdrBuf)
encodedPayload := base64.RawURLEncoding.EncodeToString(payload)
signingInput := strings.Join(
[]string{
encodedHdr,
encodedPayload,
}, ".",
)
signer, err := sign.New(alg)
if err != nil {
return nil, fmt.Errorf("failed to create signer: %w", err)
}
var signature []byte
switch s := signer.(type) {
case *sign.ECDSASigner:
signature, err = s.SignWithRand([]byte(signingInput), key, rnd)
default:
signature, err = signer.Sign([]byte(signingInput), key)
}
if err != nil {
return nil, fmt.Errorf("failed to sign Payload: %w", err)
}
encodedSignature := base64.RawURLEncoding.EncodeToString(signature)
compactSerialization := strings.Join(
[]string{
signingInput,
encodedSignature,
}, ".",
)
return []byte(compactSerialization), nil
}
// SignWithOption generates a Signature for the given Payload, and serializes
// it in compact serialization format. In this format you may NOT use
// multiple signers.
//
// If you would like to pass custom Headers, use the WithHeaders option.
func SignWithOption(payload []byte, alg jwa.SignatureAlgorithm, key any) ([]byte, error) {
var headers Headers = &StandardHeaders{}
err := headers.Set(AlgorithmKey, alg)
if err != nil {
return nil, fmt.Errorf("failed to set alg value: %w", err)
}
hdrBuf, err := json.Marshal(headers)
if err != nil {
return nil, fmt.Errorf("failed to marshal Headers: %w", err)
}
// NOTE(sr): we don't use SignWithOption -- if we did, this rand.Reader
// should come from the BuiltinContext's Seed, too.
return SignLiteral(payload, alg, key, hdrBuf, rand.Reader)
}
// Verify checks if the given JWS message is verifiable using `alg` and `key`.
// If the verification is successful, `err` is nil, and the content of the
// Payload that was signed is returned. If you need more fine-grained
// control of the verification process, manually call `Parse`, generate a
// verifier, and call `Verify` on the parsed JWS message object.
func Verify(buf []byte, alg jwa.SignatureAlgorithm, key any) (ret []byte, err error) {
verifier, err := verify.New(alg)
if err != nil {
return nil, fmt.Errorf("failed to create verifier: %w", err)
}
buf = bytes.TrimSpace(buf)
if len(buf) == 0 {
return nil, errors.New(`attempt to verify empty buffer`)
}
parts, err := SplitCompact(string(buf))
if err != nil {
return nil, fmt.Errorf("failed extract from compact serialization format: %w", err)
}
signingInput := strings.Join(
[]string{
parts[0],
parts[1],
}, ".",
)
decodedSignature, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return nil, fmt.Errorf("failed to decode signature: %w", err)
}
if err := verifier.Verify([]byte(signingInput), decodedSignature, key); err != nil {
return nil, fmt.Errorf("failed to verify message: %w", err)
}
if decodedPayload, err := base64.RawURLEncoding.DecodeString(parts[1]); err == nil {
return decodedPayload, nil
}
return nil, fmt.Errorf("failed to decode Payload: %w", err)
}
// VerifyWithJWK verifies the JWS message using the specified JWK
func VerifyWithJWK(buf []byte, key jwk.Key) (payload []byte, err error) {
keyVal, err := key.Materialize()
if err != nil {
return nil, fmt.Errorf("failed to materialize key: %w", err)
}
return Verify(buf, key.GetAlgorithm(), keyVal)
}
// VerifyWithJWKSet verifies the JWS message using JWK key set.
// By default it will only pick up keys that have the "use" key
// set to either "sig" or "enc", but you can override it by
// providing a keyaccept function.
func VerifyWithJWKSet(buf []byte, keyset *jwk.Set) (payload []byte, err error) {
for _, key := range keyset.Keys {
payload, err := VerifyWithJWK(buf, key)
if err == nil {
return payload, nil
}
}
return nil, errors.New("failed to verify with any of the keys")
}
// ParseByte parses a JWS value serialized via compact serialization and provided as []byte.
func ParseByte(jwsCompact []byte) (m *Message, err error) {
return parseCompact(string(jwsCompact))
}
// ParseString parses a JWS value serialized via compact serialization and provided as string.
func ParseString(s string) (*Message, error) {
return parseCompact(s)
}
// SplitCompact splits a JWT and returns its three parts
// separately: Protected Headers, Payload and Signature.
func SplitCompact(jwsCompact string) ([]string, error) {
parts := strings.Split(jwsCompact, ".")
if len(parts) < 3 {
return nil, errors.New("failed to split compact serialization")
}
return parts, nil
}
// parseCompact parses a JWS value serialized via compact serialization.
func parseCompact(str string) (m *Message, err error) {
var decodedHeader, decodedPayload, decodedSignature []byte
parts, err := SplitCompact(str)
if err != nil {
return nil, fmt.Errorf("invalid compact serialization format: %w", err)
}
if decodedHeader, err = base64.RawURLEncoding.DecodeString(parts[0]); err != nil {
return nil, fmt.Errorf("failed to decode Headers: %w", err)
}
var hdr StandardHeaders
if err := json.Unmarshal(decodedHeader, &hdr); err != nil {
return nil, fmt.Errorf("failed to parse JOSE Headers: %w", err)
}
if decodedPayload, err = base64.RawURLEncoding.DecodeString(parts[1]); err != nil {
return nil, fmt.Errorf("failed to decode Payload: %w", err)
}
if len(parts) > 2 {
if decodedSignature, err = base64.RawURLEncoding.DecodeString(parts[2]); err != nil {
return nil, fmt.Errorf("failed to decode Signature: %w", err)
}
}
var msg Message
msg.Payload = decodedPayload
msg.Signatures = append(msg.Signatures, &Signature{
Protected: &hdr,
Signature: decodedSignature,
})
return &msg, nil
}
-26
View File
@@ -1,26 +0,0 @@
package jws
// PublicHeaders returns the public headers in a JWS
func (s Signature) PublicHeaders() Headers {
return s.Headers
}
// ProtectedHeaders returns the protected headers in a JWS
func (s Signature) ProtectedHeaders() Headers {
return s.Protected
}
// GetSignature returns the signature in a JWS
func (s Signature) GetSignature() []byte {
return s.Signature
}
// GetPayload returns the payload in a JWS
func (m Message) GetPayload() []byte {
return m.Payload
}
// GetSignatures returns the all signatures in a JWS
func (m Message) GetSignatures() []*Signature {
return m.Signatures
}
-90
View File
@@ -1,90 +0,0 @@
package sign
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"errors"
"fmt"
"io"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
var ecdsaSignFuncs = map[jwa.SignatureAlgorithm]ecdsaSignFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]crypto.Hash{
jwa.ES256: crypto.SHA256,
jwa.ES384: crypto.SHA384,
jwa.ES512: crypto.SHA512,
}
for alg, h := range algs {
ecdsaSignFuncs[alg] = makeECDSASignFunc(h)
}
}
func makeECDSASignFunc(hash crypto.Hash) ecdsaSignFunc {
return ecdsaSignFunc(func(payload []byte, key *ecdsa.PrivateKey, rnd io.Reader) ([]byte, error) {
curveBits := key.Curve.Params().BitSize
keyBytes := curveBits / 8
// Curve bits do not need to be a multiple of 8.
if curveBits%8 > 0 {
keyBytes++
}
h := hash.New()
h.Write(payload)
r, s, err := ecdsa.Sign(rnd, key, h.Sum(nil))
if err != nil {
return nil, fmt.Errorf("failed to sign payload using ecdsa: %w", err)
}
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
out := append(rBytesPadded, sBytesPadded...)
return out, nil
})
}
func newECDSA(alg jwa.SignatureAlgorithm) (*ECDSASigner, error) {
signfn, ok := ecdsaSignFuncs[alg]
if !ok {
return nil, fmt.Errorf("unsupported algorithm while trying to create ECDSA signer: %s", alg)
}
return &ECDSASigner{
alg: alg,
sign: signfn,
}, nil
}
// Algorithm returns the signer algorithm
func (s ECDSASigner) Algorithm() jwa.SignatureAlgorithm {
return s.alg
}
// SignWithRand signs payload with a ECDSA private key and a provided randomness
// source (such as `rand.Reader`).
func (s ECDSASigner) SignWithRand(payload []byte, key any, r io.Reader) ([]byte, error) {
if key == nil {
return nil, errors.New("missing private key while signing payload")
}
privateKey, ok := key.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("invalid key type %T. *ecdsa.PrivateKey is required", key)
}
return s.sign(payload, privateKey, r)
}
// Sign signs payload with a ECDSA private key
func (s ECDSASigner) Sign(payload []byte, key any) ([]byte, error) {
return s.SignWithRand(payload, key, rand.Reader)
}
-66
View File
@@ -1,66 +0,0 @@
package sign
import (
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"errors"
"fmt"
"hash"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
var hmacSignFuncs = map[jwa.SignatureAlgorithm]hmacSignFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]func() hash.Hash{
jwa.HS256: sha256.New,
jwa.HS384: sha512.New384,
jwa.HS512: sha512.New,
}
for alg, h := range algs {
hmacSignFuncs[alg] = makeHMACSignFunc(h)
}
}
func newHMAC(alg jwa.SignatureAlgorithm) (*HMACSigner, error) {
signer, ok := hmacSignFuncs[alg]
if !ok {
return nil, fmt.Errorf(`unsupported algorithm while trying to create HMAC signer: %s`, alg)
}
return &HMACSigner{
alg: alg,
sign: signer,
}, nil
}
func makeHMACSignFunc(hfunc func() hash.Hash) hmacSignFunc {
return hmacSignFunc(func(payload []byte, key []byte) ([]byte, error) {
h := hmac.New(hfunc, key)
h.Write(payload)
return h.Sum(nil), nil
})
}
// Algorithm returns the signer algorithm
func (s HMACSigner) Algorithm() jwa.SignatureAlgorithm {
return s.alg
}
// Sign signs payload with a Symmetric key
func (s HMACSigner) Sign(payload []byte, key any) ([]byte, error) {
hmackey, ok := key.([]byte)
if !ok {
return nil, fmt.Errorf(`invalid key type %T. []byte is required`, key)
}
if len(hmackey) == 0 {
return nil, errors.New(`missing key while signing payload`)
}
return s.sign(payload, hmackey)
}
@@ -1,46 +0,0 @@
package sign
import (
"crypto/ecdsa"
"crypto/rsa"
"io"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
// Signer provides a common interface for supported alg signing methods
type Signer interface {
// Sign creates a signature for the given `payload`.
// `key` is the key used for signing the payload, and is usually
// the private key type associated with the signature method. For example,
// for `jwa.RSXXX` and `jwa.PSXXX` types, you need to pass the
// `*"crypto/rsa".PrivateKey` type.
// Check the documentation for each signer for details
Sign(payload []byte, key any) ([]byte, error)
Algorithm() jwa.SignatureAlgorithm
}
type rsaSignFunc func([]byte, *rsa.PrivateKey) ([]byte, error)
// RSASigner uses crypto/rsa to sign the payloads.
type RSASigner struct {
alg jwa.SignatureAlgorithm
sign rsaSignFunc
}
type ecdsaSignFunc func([]byte, *ecdsa.PrivateKey, io.Reader) ([]byte, error)
// ECDSASigner uses crypto/ecdsa to sign the payloads.
type ECDSASigner struct {
alg jwa.SignatureAlgorithm
sign ecdsaSignFunc
}
type hmacSignFunc func([]byte, []byte) ([]byte, error)
// HMACSigner uses crypto/hmac to sign the payloads.
type HMACSigner struct {
alg jwa.SignatureAlgorithm
sign hmacSignFunc
}
-97
View File
@@ -1,97 +0,0 @@
package sign
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
var rsaSignFuncs = map[jwa.SignatureAlgorithm]rsaSignFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]struct {
Hash crypto.Hash
SignFunc func(crypto.Hash) rsaSignFunc
}{
jwa.RS256: {
Hash: crypto.SHA256,
SignFunc: makeSignPKCS1v15,
},
jwa.RS384: {
Hash: crypto.SHA384,
SignFunc: makeSignPKCS1v15,
},
jwa.RS512: {
Hash: crypto.SHA512,
SignFunc: makeSignPKCS1v15,
},
jwa.PS256: {
Hash: crypto.SHA256,
SignFunc: makeSignPSS,
},
jwa.PS384: {
Hash: crypto.SHA384,
SignFunc: makeSignPSS,
},
jwa.PS512: {
Hash: crypto.SHA512,
SignFunc: makeSignPSS,
},
}
for alg, item := range algs {
rsaSignFuncs[alg] = item.SignFunc(item.Hash)
}
}
func makeSignPKCS1v15(hash crypto.Hash) rsaSignFunc {
return rsaSignFunc(func(payload []byte, key *rsa.PrivateKey) ([]byte, error) {
h := hash.New()
h.Write(payload)
return rsa.SignPKCS1v15(rand.Reader, key, hash, h.Sum(nil))
})
}
func makeSignPSS(hash crypto.Hash) rsaSignFunc {
return rsaSignFunc(func(payload []byte, key *rsa.PrivateKey) ([]byte, error) {
h := hash.New()
h.Write(payload)
return rsa.SignPSS(rand.Reader, key, hash, h.Sum(nil), &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
})
})
}
func newRSA(alg jwa.SignatureAlgorithm) (*RSASigner, error) {
signfn, ok := rsaSignFuncs[alg]
if !ok {
return nil, fmt.Errorf(`unsupported algorithm while trying to create RSA signer: %s`, alg)
}
return &RSASigner{
alg: alg,
sign: signfn,
}, nil
}
// Algorithm returns the signer algorithm
func (s RSASigner) Algorithm() jwa.SignatureAlgorithm {
return s.alg
}
// Sign creates a signature using crypto/rsa. key must be a non-nil instance of
// `*"crypto/rsa".PrivateKey`.
func (s RSASigner) Sign(payload []byte, key any) ([]byte, error) {
if key == nil {
return nil, errors.New(`missing private key while signing payload`)
}
rsakey, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf(`invalid key type %T. *rsa.PrivateKey is required`, key)
}
return s.sign(payload, rsakey)
}
-66
View File
@@ -1,66 +0,0 @@
package sign
import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
// New creates a signer that signs payloads using the given signature algorithm.
func New(alg jwa.SignatureAlgorithm) (Signer, error) {
switch alg {
case jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512:
return newRSA(alg)
case jwa.ES256, jwa.ES384, jwa.ES512:
return newECDSA(alg)
case jwa.HS256, jwa.HS384, jwa.HS512:
return newHMAC(alg)
default:
return nil, fmt.Errorf(`unsupported signature algorithm %s`, alg)
}
}
// GetSigningKey returns a *rsa.PrivateKey or *ecdsa.PrivateKey typically encoded in PEM blocks of type "RSA PRIVATE KEY"
// or "EC PRIVATE KEY" for RSA and ECDSA family of algorithms.
// For HMAC family, it return a []byte value
func GetSigningKey(key string, alg jwa.SignatureAlgorithm) (any, error) {
switch alg {
case jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512:
block, _ := pem.Decode([]byte(key))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
pkcs8priv, err2 := x509.ParsePKCS8PrivateKey(block.Bytes)
if err2 != nil {
return nil, fmt.Errorf("error parsing private key (%v), (%v)", err, err2)
}
return pkcs8priv, nil
}
return priv, nil
case jwa.ES256, jwa.ES384, jwa.ES512:
block, _ := pem.Decode([]byte(key))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
pkcs8priv, err2 := x509.ParsePKCS8PrivateKey(block.Bytes)
if err2 != nil {
return nil, fmt.Errorf("error parsing private key (%v), (%v)", err, err2)
}
return pkcs8priv, nil
}
return priv, nil
case jwa.HS256, jwa.HS384, jwa.HS512:
return []byte(key), nil
default:
return nil, fmt.Errorf("unsupported signature algorithm: %s", alg)
}
}
@@ -1,67 +0,0 @@
package verify
import (
"crypto"
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
var ecdsaVerifyFuncs = map[jwa.SignatureAlgorithm]ecdsaVerifyFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]crypto.Hash{
jwa.ES256: crypto.SHA256,
jwa.ES384: crypto.SHA384,
jwa.ES512: crypto.SHA512,
}
for alg, h := range algs {
ecdsaVerifyFuncs[alg] = makeECDSAVerifyFunc(h)
}
}
func makeECDSAVerifyFunc(hash crypto.Hash) ecdsaVerifyFunc {
return ecdsaVerifyFunc(func(payload []byte, signature []byte, key *ecdsa.PublicKey) error {
r, s := &big.Int{}, &big.Int{}
n := len(signature) / 2
r.SetBytes(signature[:n])
s.SetBytes(signature[n:])
h := hash.New()
h.Write(payload)
if !ecdsa.Verify(key, h.Sum(nil), r, s) {
return errors.New(`failed to verify signature using ecdsa`)
}
return nil
})
}
func newECDSA(alg jwa.SignatureAlgorithm) (*ECDSAVerifier, error) {
verifyfn, ok := ecdsaVerifyFuncs[alg]
if !ok {
return nil, fmt.Errorf(`unsupported algorithm while trying to create ECDSA verifier: %s`, alg)
}
return &ECDSAVerifier{
verify: verifyfn,
}, nil
}
// Verify checks whether the signature for a given input and key is correct
func (v ECDSAVerifier) Verify(payload []byte, signature []byte, key any) error {
if key == nil {
return errors.New(`missing public key while verifying payload`)
}
ecdsakey, ok := key.(*ecdsa.PublicKey)
if !ok {
return fmt.Errorf(`invalid key type %T. *ecdsa.PublicKey is required`, key)
}
return v.verify(payload, signature, ecdsakey)
}
@@ -1,33 +0,0 @@
package verify
import (
"crypto/hmac"
"errors"
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
"github.com/open-policy-agent/opa/internal/jwx/jws/sign"
)
func newHMAC(alg jwa.SignatureAlgorithm) (*HMACVerifier, error) {
s, err := sign.New(alg)
if err != nil {
return nil, fmt.Errorf("failed to generate HMAC signer: %w", err)
}
return &HMACVerifier{signer: s}, nil
}
// Verify checks whether the signature for a given input and key is correct
func (v HMACVerifier) Verify(signingInput, signature []byte, key any) (err error) {
expected, err := v.signer.Sign(signingInput, key)
if err != nil {
return fmt.Errorf("failed to generated signature: %w", err)
}
if !hmac.Equal(signature, expected) {
return errors.New("failed to match hmac signature")
}
return nil
}
@@ -1,39 +0,0 @@
package verify
import (
"crypto/ecdsa"
"crypto/rsa"
"github.com/open-policy-agent/opa/internal/jwx/jws/sign"
)
// Verifier provides a common interface for supported alg verification methods
type Verifier interface {
// Verify checks whether the payload and signature are valid for
// the given key.
// `key` is the key used for verifying the payload, and is usually
// the public key associated with the signature method. For example,
// for `jwa.RSXXX` and `jwa.PSXXX` types, you need to pass the
// `*"crypto/rsa".PublicKey` type.
// Check the documentation for each verifier for details
Verify(payload []byte, signature []byte, key any) error
}
type rsaVerifyFunc func([]byte, []byte, *rsa.PublicKey) error
// RSAVerifier implements the Verifier interface
type RSAVerifier struct {
verify rsaVerifyFunc
}
type ecdsaVerifyFunc func([]byte, []byte, *ecdsa.PublicKey) error
// ECDSAVerifier implements the Verifier interface
type ECDSAVerifier struct {
verify ecdsaVerifyFunc
}
// HMACVerifier implements the Verifier interface
type HMACVerifier struct {
signer sign.Signer
}
-88
View File
@@ -1,88 +0,0 @@
package verify
import (
"crypto"
"crypto/rsa"
"errors"
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
var rsaVerifyFuncs = map[jwa.SignatureAlgorithm]rsaVerifyFunc{}
func init() {
algs := map[jwa.SignatureAlgorithm]struct {
Hash crypto.Hash
VerifyFunc func(crypto.Hash) rsaVerifyFunc
}{
jwa.RS256: {
Hash: crypto.SHA256,
VerifyFunc: makeVerifyPKCS1v15,
},
jwa.RS384: {
Hash: crypto.SHA384,
VerifyFunc: makeVerifyPKCS1v15,
},
jwa.RS512: {
Hash: crypto.SHA512,
VerifyFunc: makeVerifyPKCS1v15,
},
jwa.PS256: {
Hash: crypto.SHA256,
VerifyFunc: makeVerifyPSS,
},
jwa.PS384: {
Hash: crypto.SHA384,
VerifyFunc: makeVerifyPSS,
},
jwa.PS512: {
Hash: crypto.SHA512,
VerifyFunc: makeVerifyPSS,
},
}
for alg, item := range algs {
rsaVerifyFuncs[alg] = item.VerifyFunc(item.Hash)
}
}
func makeVerifyPKCS1v15(hash crypto.Hash) rsaVerifyFunc {
return rsaVerifyFunc(func(payload, signature []byte, key *rsa.PublicKey) error {
h := hash.New()
h.Write(payload)
return rsa.VerifyPKCS1v15(key, hash, h.Sum(nil), signature)
})
}
func makeVerifyPSS(hash crypto.Hash) rsaVerifyFunc {
return rsaVerifyFunc(func(payload, signature []byte, key *rsa.PublicKey) error {
h := hash.New()
h.Write(payload)
return rsa.VerifyPSS(key, hash, h.Sum(nil), signature, nil)
})
}
func newRSA(alg jwa.SignatureAlgorithm) (*RSAVerifier, error) {
verifyfn, ok := rsaVerifyFuncs[alg]
if !ok {
return nil, fmt.Errorf(`unsupported algorithm while trying to create RSA verifier: %s`, alg)
}
return &RSAVerifier{
verify: verifyfn,
}, nil
}
// Verify checks if a JWS is valid.
func (v RSAVerifier) Verify(payload, signature []byte, key any) error {
if key == nil {
return errors.New(`missing public key while verifying payload`)
}
rsaKey, ok := key.(*rsa.PublicKey)
if !ok {
return fmt.Errorf(`invalid key type %T. *rsa.PublicKey is required`, key)
}
return v.verify(payload, signature, rsaKey)
}
@@ -1,56 +0,0 @@
package verify
import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
)
// New creates a new JWS verifier using the specified algorithm
// and the public key
func New(alg jwa.SignatureAlgorithm) (Verifier, error) {
switch alg {
case jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512:
return newRSA(alg)
case jwa.ES256, jwa.ES384, jwa.ES512:
return newECDSA(alg)
case jwa.HS256, jwa.HS384, jwa.HS512:
return newHMAC(alg)
default:
return nil, fmt.Errorf(`unsupported signature algorithm: %s`, alg)
}
}
// GetSigningKey returns a *rsa.PublicKey or *ecdsa.PublicKey typically encoded in PEM blocks of type "PUBLIC KEY",
// for RSA and ECDSA family of algorithms.
// For HMAC family, it return a []byte value
func GetSigningKey(key string, alg jwa.SignatureAlgorithm) (any, error) {
switch alg {
case jwa.RS256, jwa.RS384, jwa.RS512, jwa.PS256, jwa.PS384, jwa.PS512, jwa.ES256, jwa.ES384, jwa.ES512:
block, _ := pem.Decode([]byte(key))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := pub.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey:
return pub, nil
default:
return nil, fmt.Errorf("invalid key type %T", pub)
}
case jwa.HS256, jwa.HS384, jwa.HS512:
return []byte(key), nil
default:
return nil, fmt.Errorf("unsupported signature algorithm: %s", alg)
}
}
+77 -43
View File
@@ -6,17 +6,19 @@
package report
import (
"cmp"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/open-policy-agent/opa/internal/semver"
"github.com/open-policy-agent/opa/v1/keys"
"github.com/open-policy-agent/opa/v1/logging"
"github.com/open-policy-agent/opa/v1/version"
@@ -25,24 +27,25 @@ import (
"github.com/open-policy-agent/opa/v1/util"
)
// ExternalServiceURL is the base HTTP URL for a telemetry service.
// If not otherwise specified it will use the hard coded default.
// ExternalServiceURL is the base HTTP URL for a github instance used
// to query for more recent version.
// If not otherwise specified, it will use the hard-coded default, api.github.com.
// GHRepo is the repository to use, and defaults to "open-policy-agent/opa"
//
// Override at build time via:
//
// -ldflags "-X github.com/open-policy-agent/opa/internal/report.ExternalServiceURL=<url>"
// -ldflags "-X github.com/open-policy-agent/opa/internal/report.GHRepo=<url>"
//
// This will be overridden if the OPA_TELEMETRY_SERVICE_URL environment variable
// ExternalServiceURL will be overridden if the OPA_TELEMETRY_SERVICE_URL environment variable
// is provided.
var ExternalServiceURL = "https://telemetry.openpolicyagent.org"
var ExternalServiceURL = "https://api.github.com"
var GHRepo = "open-policy-agent/opa"
// Reporter reports information such as the version, heap usage about the running OPA instance to an external service
type Reporter struct {
body map[string]any
client rest.Client
gatherers map[string]Gatherer
gatherersMtx sync.Mutex
type Reporter interface {
SendReport(ctx context.Context) (*DataResponse, error)
RegisterGatherer(key string, f Gatherer)
}
// Gatherer represents a mechanism to inject additional data in the telemetry report
@@ -50,7 +53,7 @@ type Gatherer func(ctx context.Context) (any, error)
// DataResponse represents the data returned by the external service
type DataResponse struct {
Latest ReleaseDetails `json:"latest,omitempty"`
Latest ReleaseDetails `json:"latest"`
}
// ReleaseDetails holds information about the latest OPA release
@@ -66,20 +69,21 @@ type Options struct {
Logger logging.Logger
}
// New returns an instance of the Reporter
func New(id string, opts Options) (*Reporter, error) {
r := Reporter{
gatherers: map[string]Gatherer{},
}
r.body = map[string]any{
"id": id,
"version": version.Version,
}
type GHVersionCollector struct {
client rest.Client
}
url := os.Getenv("OPA_TELEMETRY_SERVICE_URL")
if url == "" {
url = ExternalServiceURL
}
type GHResponse struct {
TagName string `json:"tag_name,omitempty"` // latest OPA release tag
ReleaseNotes string `json:"html_url,omitempty"` // link to the OPA release notes
Download string `json:"assets_url,omitempty"` // link to download the OPA release
}
// New returns an instance of the Reporter
func New(opts Options) (Reporter, error) {
r := GHVersionCollector{}
url := cmp.Or(os.Getenv("OPA_TELEMETRY_SERVICE_URL"), ExternalServiceURL)
restConfig := fmt.Appendf(nil, `{
"url": %q,
@@ -99,21 +103,11 @@ func New(id string, opts Options) (*Reporter, error) {
// SendReport sends the telemetry report which includes information such as the OPA version, current memory usage to
// the external service
func (r *Reporter) SendReport(ctx context.Context) (*DataResponse, error) {
func (r *GHVersionCollector) SendReport(ctx context.Context) (*DataResponse, error) {
rCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
r.gatherersMtx.Lock()
defer r.gatherersMtx.Unlock()
for key, g := range r.gatherers {
var err error
r.body[key], err = g(rCtx)
if err != nil {
return nil, fmt.Errorf("gather telemetry error for key %s: %w", key, err)
}
}
resp, err := r.client.WithJSON(r.body).Do(rCtx, "POST", "/v1/version")
resp, err := r.client.Do(rCtx, "GET", fmt.Sprintf("/repos/%s/releases/latest", GHRepo))
if err != nil {
return nil, err
}
@@ -123,12 +117,12 @@ func (r *Reporter) SendReport(ctx context.Context) (*DataResponse, error) {
switch resp.StatusCode {
case http.StatusOK:
if resp.Body != nil {
var result DataResponse
var result GHResponse
err := json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return nil, err
}
return &result, nil
return createDataResponse(result)
}
return nil, nil
default:
@@ -136,10 +130,50 @@ func (r *Reporter) SendReport(ctx context.Context) (*DataResponse, error) {
}
}
func (r *Reporter) RegisterGatherer(key string, f Gatherer) {
r.gatherersMtx.Lock()
r.gatherers[key] = f
r.gatherersMtx.Unlock()
func createDataResponse(ghResp GHResponse) (*DataResponse, error) {
if ghResp.TagName == "" {
return nil, errors.New("server response does not contain tag_name")
}
v := strings.TrimPrefix(version.Version, "v")
sv, err := semver.NewVersion(v)
if err != nil {
return nil, fmt.Errorf("failed to parse current version %q: %w", v, err)
}
latestV := strings.TrimPrefix(ghResp.TagName, "v")
latestSV, err := semver.NewVersion(latestV)
if err != nil {
return nil, fmt.Errorf("failed to parse latest version %q: %w", latestV, err)
}
isLatest := sv.Compare(*latestSV) >= 0
// Note: alternatively, we could look through the assets in the GH API response to find a matching asset,
// and use its URL. However, this is not guaranteed to be more robust, and wouldn't use the 'openpolicyagent.org' domain.
downloadLink := fmt.Sprintf("https://openpolicyagent.org/downloads/%v/opa_%v_%v",
ghResp.TagName, runtime.GOOS, runtime.GOARCH)
if runtime.GOARCH == "arm64" {
downloadLink = fmt.Sprintf("%v_static", downloadLink)
}
if strings.HasPrefix(runtime.GOOS, "win") {
downloadLink = fmt.Sprintf("%v.exe", downloadLink)
}
return &DataResponse{
Latest: ReleaseDetails{
Download: downloadLink,
ReleaseNotes: ghResp.ReleaseNotes,
LatestRelease: ghResp.TagName,
OPAUpToDate: isLatest,
},
}, nil
}
func (*GHVersionCollector) RegisterGatherer(_ string, _ Gatherer) {
// no-op for this implementation
}
// IsSet returns true if dr is populated.
+7 -1
View File
@@ -29,6 +29,7 @@ type InsertAndCompileOptions struct {
MaxErrors int
EnablePrintStatements bool
ParserOptions ast.ParserOptions
BundleActivatorPlugin string
}
// InsertAndCompileResult contains the output of the operation.
@@ -68,6 +69,7 @@ func InsertAndCompile(ctx context.Context, opts InsertAndCompileOptions) (*Inser
Bundles: opts.Bundles,
ExtraModules: policies,
ParserOptions: opts.ParserOptions,
Plugin: opts.BundleActivatorPlugin,
}
err := bundle.Activate(activation)
@@ -122,10 +124,11 @@ func LoadPaths(paths []string,
asBundle bool,
bvc *bundle.VerificationConfig,
skipVerify bool,
bundleLazyLoading bool,
processAnnotations bool,
caps *ast.Capabilities,
fsys fs.FS) (*LoadPathsResult, error) {
return LoadPathsForRegoVersion(ast.RegoV0, paths, filter, asBundle, bvc, skipVerify, processAnnotations, false, caps, fsys)
return LoadPathsForRegoVersion(ast.RegoV0, paths, filter, asBundle, bvc, skipVerify, bundleLazyLoading, processAnnotations, false, caps, fsys)
}
func LoadPathsForRegoVersion(regoVersion ast.RegoVersion,
@@ -134,6 +137,7 @@ func LoadPathsForRegoVersion(regoVersion ast.RegoVersion,
asBundle bool,
bvc *bundle.VerificationConfig,
skipVerify bool,
bundleLazyLoading bool,
processAnnotations bool,
followSymlinks bool,
caps *ast.Capabilities,
@@ -159,6 +163,7 @@ func LoadPathsForRegoVersion(regoVersion ast.RegoVersion,
WithFS(fsys).
WithBundleVerificationConfig(bvc).
WithSkipBundleVerification(skipVerify).
WithBundleLazyLoadingMode(bundleLazyLoading).
WithFilter(filter).
WithProcessAnnotation(processAnnotations).
WithCapabilities(caps).
@@ -177,6 +182,7 @@ func LoadPathsForRegoVersion(regoVersion ast.RegoVersion,
files, err := loader.NewFileLoader().
WithFS(fsys).
WithBundleLazyLoadingMode(bundleLazyLoading).
WithProcessAnnotation(processAnnotations).
WithCapabilities(caps).
WithRegoVersion(regoVersion).
+3
View File
@@ -19,6 +19,9 @@ type Store = v1.Store
// generic MakeDir functionality in storage.MakeDir
type MakeDirer = v1.MakeDirer
// NonEmptyer allows a store implemention to override NonEmpty())
type NonEmptyer = v1.NonEmptyer
// TransactionParams describes a new transaction.
type TransactionParams = v1.TransactionParams
+21 -5
View File
@@ -198,6 +198,7 @@ var DefaultBuiltins = [...]*Builtin{
JWTVerifyES256,
JWTVerifyES384,
JWTVerifyES512,
JWTVerifyEdDSA,
JWTVerifyHS256,
JWTVerifyHS384,
JWTVerifyHS512,
@@ -769,7 +770,7 @@ var aggregates = category("aggregates")
var Count = &Builtin{
Name: "count",
Description: " Count takes a collection or string and returns the number of elements (or characters) in it.",
Description: "Count takes a collection or string and returns the number of elements (or characters) in it.",
Decl: types.NewFunction(
types.Args(
types.Named("collection", types.NewAny(
@@ -926,7 +927,7 @@ var ToNumber = &Builtin{
types.N,
types.S,
types.B,
types.NewNull(),
types.Nl,
)).Description("value to convert"),
),
types.Named("num", types.N).Description("the numeric representation of `x`"),
@@ -2236,6 +2237,20 @@ var JWTVerifyES512 = &Builtin{
canSkipBctx: false,
}
var JWTVerifyEdDSA = &Builtin{
Name: "io.jwt.verify_eddsa",
Description: "Verifies if an EdDSA JWT signature is valid.",
Decl: types.NewFunction(
types.Args(
types.Named("jwt", types.S).Description("JWT token whose signature is to be verified"),
types.Named("certificate", types.S).Description("PEM encoded certificate, PEM encoded public key, or the JWK key (set) used to verify the signature"),
),
types.Named("result", types.B).Description("`true` if the signature is valid, `false` otherwise"),
),
Categories: tokensCat,
canSkipBctx: false,
}
var JWTVerifyHS256 = &Builtin{
Name: "io.jwt.verify_hs256",
Description: "Verifies if a HS256 (secret) JWT signature is valid.",
@@ -2282,7 +2297,7 @@ var JWTVerifyHS512 = &Builtin{
var JWTDecodeVerify = &Builtin{
Name: "io.jwt.decode_verify",
Description: `Verifies a JWT signature under parameterized constraints and decodes the claims if it is valid.
Supports the following algorithms: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384 and PS512.`,
Supports the following algorithms: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, and EdDSA.`,
Decl: types.NewFunction(
types.Args(
types.Named("jwt", types.S).Description("JWT token whose signature is to be verified and whose claims are to be checked"),
@@ -2573,6 +2588,7 @@ var CryptoX509ParseKeyPair = &Builtin{
),
canSkipBctx: true,
}
var CryptoX509ParseRSAPrivateKey = &Builtin{
Name: "crypto.x509.parse_rsa_private_key",
Description: "Returns a JWK for signing a JWT from the given PEM-encoded RSA private key.",
@@ -3172,7 +3188,7 @@ var GlobMatch = &Builtin{
types.Named("pattern", types.S).Description("glob pattern"),
types.Named("delimiters", types.NewAny(
types.NewArray(nil, types.S),
types.NewNull(),
types.Nl,
)).Description("glob pattern delimiters, e.g. `[\".\", \":\"]`, defaults to `[\".\"]` if unset. If `delimiters` is `null`, glob match without delimiter."),
types.Named("match", types.S).Description("string to match against `pattern`"),
),
@@ -3453,7 +3469,7 @@ var CastNull = &Builtin{
Name: "cast_null",
Decl: types.NewFunction(
types.Args(types.A),
types.NewNull(),
types.Nl,
),
deprecated: true,
canSkipBctx: true,
+28 -9
View File
@@ -14,6 +14,7 @@ import (
"slices"
"sort"
"strings"
"sync"
"github.com/open-policy-agent/opa/internal/semver"
"github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities"
@@ -38,14 +39,15 @@ type VersionIndex struct {
//go:embed version_index.json
var versionIndexBs []byte
var minVersionIndex = func() VersionIndex {
// init only on demand, as JSON unmarshalling comes with some cost, and contributes
// noise to things like pprof stats
var minVersionIndexOnce = sync.OnceValue(func() VersionIndex {
var vi VersionIndex
err := json.Unmarshal(versionIndexBs, &vi)
if err != nil {
if err := json.Unmarshal(versionIndexBs, &vi); err != nil {
panic(err)
}
return vi
}()
})
// In the compiler, we used this to check that we're OK working with ref heads.
// If this isn't present, we'll fail. This is to ensure that older versions of
@@ -57,6 +59,24 @@ const FeatureRegoV1 = "rego_v1"
const FeatureRegoV1Import = "rego_v1_import"
const FeatureKeywordsInRefs = "keywords_in_refs"
// Features carries the default features supported by this version of OPA.
// Use RegisterFeatures to add to them.
var Features = []string{
FeatureRegoV1,
FeatureKeywordsInRefs,
}
// RegisterFeatures lets applications wrapping OPA register features, to be
// included in `ast.CapabilitiesForThisVersion()`.
func RegisterFeatures(fs ...string) {
for i := range fs {
if slices.Contains(Features, fs[i]) {
continue
}
Features = append(Features, fs[i])
}
}
// Capabilities defines a structure containing data that describes the capabilities
// or features supported by a particular version of OPA.
type Capabilities struct {
@@ -141,10 +161,8 @@ func CapabilitiesForThisVersion(opts ...CapabilitiesOption) *Capabilities {
f.FutureKeywords = append(f.FutureKeywords, kw)
}
f.Features = []string{
FeatureRegoV1,
FeatureKeywordsInRefs,
}
f.Features = make([]string, len(Features))
copy(f.Features, Features)
}
sort.Strings(f.FutureKeywords)
@@ -208,7 +226,6 @@ func LoadCapabilitiesVersions() ([]string, error) {
// MinimumCompatibleVersion returns the minimum compatible OPA version based on
// the built-ins, features, and keywords in c.
func (c *Capabilities) MinimumCompatibleVersion() (string, bool) {
var maxVersion semver.Version
// this is the oldest OPA release that includes capabilities
@@ -216,6 +233,8 @@ func (c *Capabilities) MinimumCompatibleVersion() (string, bool) {
panic("unreachable")
}
minVersionIndex := minVersionIndexOnce()
for _, bi := range c.Builtins {
v, ok := minVersionIndex.Builtins[bi.Name]
if !ok {
+2 -2
View File
@@ -310,7 +310,7 @@ func (tc *typeChecker) checkRule(env *TypeEnv, as *AnnotationSet, rule *Rule) {
var err error
tpe, err = nestedObject(cpy, objPath, typeV)
if err != nil {
tc.err([]*Error{NewError(TypeErr, rule.Head.Location, err.Error())}) //nolint:govet
tc.err([]*Error{NewError(TypeErr, rule.Head.Location, "%s", err.Error())})
tpe = nil
}
} else if typeV != nil {
@@ -1318,7 +1318,7 @@ func processAnnotation(ss *SchemaSet, annot *SchemaAnnotation, rule *Rule, allow
tpe, err := loadSchema(schema, allowNet)
if err != nil {
return nil, NewError(TypeErr, rule.Location, err.Error()) //nolint:govet
return nil, NewError(TypeErr, rule.Location, "%s", err.Error())
}
return tpe, nil
+310 -162
View File
@@ -26,7 +26,11 @@ import (
// exiting.
const CompileErrorLimitDefault = 10
var errLimitReached = NewError(CompileErr, nil, "error limit reached")
var (
errLimitReached = NewError(CompileErr, nil, "error limit reached")
doubleEq = Equal.Ref()
)
// Compiler contains the state of a compilation process.
type Compiler struct {
@@ -850,7 +854,7 @@ func (c *Compiler) PassesTypeCheckRules(rules []*Rule) Errors {
tpe, err := loadSchema(schema, allowNet)
if err != nil {
return Errors{NewError(TypeErr, nil, err.Error())} //nolint:govet
return Errors{NewError(TypeErr, nil, "%s", err.Error())}
}
c.inputType = tpe
}
@@ -955,8 +959,10 @@ func (c *Compiler) buildRuleIndices() {
func (c *Compiler) buildComprehensionIndices() {
for _, name := range c.sorted {
WalkRules(c.Modules[name], func(r *Rule) bool {
candidates := r.Head.Args.Vars()
candidates.Update(ReservedVars)
candidates := ReservedVars.Copy()
if len(r.Head.Args) > 0 {
candidates.Update(r.Head.Args.Vars())
}
n := buildComprehensionIndices(c.debug, c.GetArity, candidates, c.RewrittenVars, r.Body, c.comprehensionIndices)
c.counterAdd(compileStageComprehensionIndexBuild, n)
return false
@@ -1207,7 +1213,7 @@ func (c *Compiler) checkRuleConflicts() {
continue // don't self-conflict
}
msg := fmt.Sprintf("%v conflicts with rule %v defined at %v", childMod.Package, rule.Head.Ref(), rule.Loc())
c.err(NewError(TypeErr, mod.Package.Loc(), msg)) //nolint:govet
c.err(NewError(TypeErr, mod.Package.Loc(), "%s", msg))
}
}
}
@@ -1281,7 +1287,9 @@ func (c *Compiler) checkSafetyRuleBodies() {
m := c.Modules[name]
WalkRules(m, func(r *Rule) bool {
safe := ReservedVars.Copy()
safe.Update(r.Head.Args.Vars())
if len(r.Head.Args) > 0 {
safe.Update(r.Head.Args.Vars())
}
r.Body = c.checkBodySafety(safe, r.Body)
return false
})
@@ -1310,19 +1318,24 @@ var SafetyCheckVisitorParams = VarVisitorParams{
// checkSafetyRuleHeads ensures that variables appearing in the head of a
// rule also appear in the body.
func (c *Compiler) checkSafetyRuleHeads() {
for _, name := range c.sorted {
m := c.Modules[name]
WalkRules(m, func(r *Rule) bool {
WalkRules(c.Modules[name], func(r *Rule) bool {
safe := r.Body.Vars(SafetyCheckVisitorParams)
safe.Update(r.Head.Args.Vars())
unsafe := r.Head.Vars().Diff(safe)
for v := range unsafe {
if w, ok := c.RewrittenVars[v]; ok {
v = w
}
if !v.IsGenerated() {
c.err(NewError(UnsafeVarErr, r.Loc(), "var %v is unsafe", v))
if len(r.Head.Args) > 0 {
safe.Update(r.Head.Args.Vars())
}
if headMayHaveVars(r.Head) {
vars := r.Head.Vars()
if vars.DiffCount(safe) > 0 {
unsafe := vars.Diff(safe)
for v := range unsafe {
if w, ok := c.RewrittenVars[v]; ok {
v = w
}
if !v.IsGenerated() {
c.err(NewError(UnsafeVarErr, r.Loc(), "var %v is unsafe", v))
}
}
}
}
return false
@@ -1681,6 +1694,31 @@ func (c *Compiler) init() {
return
}
if defaultModuleLoader != nil {
if c.moduleLoader == nil {
c.moduleLoader = defaultModuleLoader
} else {
first := c.moduleLoader
c.moduleLoader = func(res map[string]*Module) (map[string]*Module, error) {
res0, err := first(res)
if err != nil {
return nil, err
}
res1, err := defaultModuleLoader(res)
if err != nil {
return nil, err
}
// merge res1 into res0, based on module "file" names, to avoid clashes
for k, v := range res1 {
if _, ok := res0[k]; !ok {
res0[k] = v
}
}
return res0, nil
}
}
}
if c.capabilities == nil {
c.capabilities = CapabilitiesForThisVersion()
}
@@ -1701,7 +1739,7 @@ func (c *Compiler) init() {
if schema := c.schemaSet.Get(SchemaRootRef); schema != nil {
tpe, err := loadSchema(schema, c.capabilities.AllowNet)
if err != nil {
c.err(NewError(TypeErr, nil, err.Error())) //nolint:govet
c.err(NewError(TypeErr, nil, "%s", err.Error()))
} else {
c.inputType = tpe
}
@@ -1869,7 +1907,7 @@ func (c *Compiler) resolveAllRefs() {
WalkRules(mod, func(rule *Rule) bool {
err := resolveRefsInRule(globals, rule)
if err != nil {
c.err(NewError(CompileErr, rule.Location, err.Error())) //nolint:govet
c.err(NewError(CompileErr, rule.Location, "%s", err.Error()))
}
return false
})
@@ -1894,7 +1932,7 @@ func (c *Compiler) resolveAllRefs() {
parsed, err := c.moduleLoader(c.Modules)
if err != nil {
c.err(NewError(CompileErr, nil, err.Error())) //nolint:govet
c.err(NewError(CompileErr, nil, "%s", err.Error()))
return
}
@@ -2127,12 +2165,16 @@ func rewritePrintCalls(gen *localVarGenerator, getArity func(Ref) int, globals V
safe.Update(globals)
args := body[i].Operands()
var vis *VarVisitor
for j := range args {
vis := NewVarVisitor().WithParams(SafetyCheckVisitorParams)
vis = vis.ClearOrNew().WithParams(SafetyCheckVisitorParams)
vis.Walk(args[j])
unsafe := vis.Vars().Diff(safe)
for _, v := range unsafe.Sorted() {
errs = append(errs, NewError(CompileErr, args[j].Loc(), "var %v is undeclared", v))
vars := vis.Vars()
if vars.DiffCount(safe) > 0 {
unsafe := vars.Diff(safe)
for _, v := range unsafe.Sorted() {
errs = append(errs, NewError(CompileErr, args[j].Loc(), "var %v is undeclared", v))
}
}
}
@@ -2140,17 +2182,17 @@ func rewritePrintCalls(gen *localVarGenerator, getArity func(Ref) int, globals V
return false, errs
}
arr := NewArray()
terms := make([]*Term, 0, len(args))
for j := range args {
x := NewTerm(gen.Generate()).SetLocation(args[j].Loc())
capture := Equality.Expr(x, args[j]).SetLocation(args[j].Loc())
arr = arr.Append(SetComprehensionTerm(x, NewBody(capture)).SetLocation(args[j].Loc()))
terms = append(terms, SetComprehensionTerm(x, NewBody(capture)).SetLocation(args[j].Loc()))
}
body.Set(NewExpr([]*Term{
NewTerm(InternalPrint.Ref()).SetLocation(body[i].Loc()),
NewTerm(arr).SetLocation(body[i].Loc()),
ArrayTerm(terms...).SetLocation(body[i].Loc()),
}).SetLocation(body[i].Loc()), i)
}
@@ -2270,8 +2312,7 @@ func (c *Compiler) rewriteRefsInHead() {
func (c *Compiler) rewriteEquals() {
modified := false
for _, name := range c.sorted {
mod := c.Modules[name]
modified = rewriteEquals(mod) || modified
modified = rewriteEquals(c.Modules[name]) || modified
}
if modified {
c.Required.addBuiltinSorted(Equal)
@@ -2281,8 +2322,7 @@ func (c *Compiler) rewriteEquals() {
func (c *Compiler) rewriteDynamicTerms() {
f := newEqualityFactory(c.localvargen)
for _, name := range c.sorted {
mod := c.Modules[name]
WalkRules(mod, func(rule *Rule) bool {
WalkRules(c.Modules[name], func(rule *Rule) bool {
rule.Body = rewriteDynamics(f, rule.Body)
return false
})
@@ -2546,19 +2586,21 @@ func createMetadataChain(chain []*AnnotationsRef) (*Term, *Error) {
}
func (c *Compiler) rewriteLocalVars() {
var assignment bool
args := NewVarVisitor()
argsStack := newLocalDeclaredVars()
for _, name := range c.sorted {
mod := c.Modules[name]
gen := c.localvargen
WalkRules(mod, func(rule *Rule) bool {
argsStack := newLocalDeclaredVars()
args.Clear()
argsStack.Clear()
args := NewVarVisitor()
if c.strict {
args.Walk(rule.Head.Args)
if c.strict && len(rule.Head.Args) > 0 {
args.WalkArgs(rule.Head.Args)
}
unusedArgs := args.Vars()
@@ -2603,45 +2645,51 @@ func (c *Compiler) rewriteLocalVars() {
}
func (c *Compiler) rewriteLocalVarsInRule(rule *Rule, unusedArgs VarSet, argsStack *localDeclaredVars, gen *localVarGenerator) (*localDeclaredVars, Errors) {
// Rewrite assignments contained in head of rule. Assignments can
// occur in rule head if they're inside a comprehension. Note,
// assigned vars in comprehensions in the head will be rewritten
// first to preserve scoping rules. For example:
//
// p = [x | x := 1] { x := 2 } becomes p = [__local0__ | __local0__ = 1] { __local1__ = 2 }
//
// This behaviour is consistent scoping inside the body. For example:
//
// p = xs { x := 2; xs = [x | x := 1] } becomes p = xs { __local0__ = 2; xs = [__local1__ | __local1__ = 1] }
nestedXform := &rewriteNestedHeadVarLocalTransform{
gen: gen,
RewrittenVars: c.RewrittenVars,
strict: c.strict,
}
onlyScalars := !headMayHaveVars(rule.Head)
NewGenericVisitor(nestedXform.Visit).Walk(rule.Head)
var used VarSet
for _, err := range nestedXform.errs {
c.err(err)
}
if !onlyScalars {
// Rewrite assignments contained in head of rule. Assignments can
// occur in rule head if they're inside a comprehension. Note,
// assigned vars in comprehensions in the head will be rewritten
// first to preserve scoping rules. For example:
//
// p = [x | x := 1] { x := 2 } becomes p = [__local0__ | __local0__ = 1] { __local1__ = 2 }
//
// This behaviour is consistent scoping inside the body. For example:
//
// p = xs { x := 2; xs = [x | x := 1] } becomes p = xs { __local0__ = 2; xs = [__local1__ | __local1__ = 1] }
nestedXform := &rewriteNestedHeadVarLocalTransform{
gen: gen,
RewrittenVars: c.RewrittenVars,
strict: c.strict,
}
// Rewrite assignments in body.
used := NewVarSet()
NewGenericVisitor(nestedXform.Visit).Walk(rule.Head)
for _, t := range rule.Head.Ref()[1:] {
used.Update(t.Vars())
}
for _, err := range nestedXform.errs {
c.err(err)
}
if rule.Head.Key != nil {
used.Update(rule.Head.Key.Vars())
}
// Rewrite assignments in body.
used = NewVarSet()
if rule.Head.Value != nil {
valueVars := rule.Head.Value.Vars()
used.Update(valueVars)
for arg := range unusedArgs {
if valueVars.Contains(arg) {
delete(unusedArgs, arg)
for _, t := range rule.Head.Ref()[1:] {
used.Update(t.Vars())
}
if rule.Head.Key != nil {
used.Update(rule.Head.Key.Vars())
}
if rule.Head.Value != nil {
valueVars := rule.Head.Value.Vars()
used.Update(valueVars)
for arg := range unusedArgs {
if valueVars.Contains(arg) {
delete(unusedArgs, arg)
}
}
}
}
@@ -2656,6 +2704,10 @@ func (c *Compiler) rewriteLocalVarsInRule(rule *Rule, unusedArgs VarSet, argsSta
rule.Body = body
if onlyScalars {
return stack, errs
}
// Rewrite vars in head that refer to locally declared vars in the body.
localXform := rewriteHeadVarLocalTransform{declared: declared}
@@ -2676,6 +2728,30 @@ func (c *Compiler) rewriteLocalVarsInRule(rule *Rule, unusedArgs VarSet, argsSta
return stack, errs
}
func headMayHaveVars(head *Head) bool {
if head == nil {
return false
}
for i := range head.Args {
if !IsScalar(head.Args[i].Value) {
return true
}
}
if head.Key != nil && !IsScalar(head.Key.Value) {
return true
}
if head.Value != nil && !IsScalar(head.Value.Value) {
return true
}
ref := head.Ref()[1:]
for i := range ref {
if !IsScalar(ref[i].Value) {
return true
}
}
return false
}
type rewriteNestedHeadVarLocalTransform struct {
gen *localVarGenerator
errs Errors
@@ -2684,9 +2760,7 @@ type rewriteNestedHeadVarLocalTransform struct {
}
func (xform *rewriteNestedHeadVarLocalTransform) Visit(x any) bool {
if term, ok := x.(*Term); ok {
stop := false
stack := newLocalDeclaredVars()
@@ -2787,7 +2861,7 @@ func (vis *ruleArgLocalRewriter) Visit(x any) Visitor {
Walk(vis, vcpy)
return k, vcpy, nil
}); err != nil {
vis.errs = append(vis.errs, NewError(CompileErr, t.Location, err.Error())) //nolint:govet
vis.errs = append(vis.errs, NewError(CompileErr, t.Location, "%s", err.Error()))
} else {
t.Value = cpy
}
@@ -3163,7 +3237,7 @@ func (ci *ComprehensionIndex) String() string {
return fmt.Sprintf("<keys: %v>", NewArray(ci.Keys...))
}
func buildComprehensionIndices(dbg debug.Debug, arity func(Ref) int, candidates VarSet, rwVars map[Var]Var, node any, result map[*Term]*ComprehensionIndex) uint64 {
func buildComprehensionIndices(dbg debug.Debug, arity func(Ref) int, candidates VarSet, rwVars map[Var]Var, node Body, result map[*Term]*ComprehensionIndex) uint64 {
var n uint64
cpy := candidates.Copy()
WalkBodies(node, func(b Body) bool {
@@ -3365,7 +3439,6 @@ func (vis *comprehensionIndexNestedCandidateVisitor) Walk(x any) {
}
func (vis *comprehensionIndexNestedCandidateVisitor) visit(x any) bool {
if vis.found {
return true
}
@@ -3904,22 +3977,27 @@ func (vs unsafeVars) Slice() (result []unsafePair) {
// If the body cannot be reordered to ensure safety, the second return value
// contains a mapping of expressions to unsafe variables in those expressions.
func reorderBodyForSafety(builtins map[string]*Builtin, arity func(Ref) int, globals VarSet, body Body) (Body, unsafeVars) {
vis := varVisitorPool.Get().WithParams(SafetyCheckVisitorParams)
vis.WalkBody(body)
bodyVars := body.Vars(SafetyCheckVisitorParams)
reordered := make(Body, 0, len(body))
safe := VarSet{}
unsafe := unsafeVars{}
defer varVisitorPool.Put(vis)
bodyVars := vis.Vars().Copy()
safe := bodyVars.Intersect(globals)
unsafe := make(unsafeVars, len(bodyVars)-len(safe))
for _, e := range body {
for v := range e.Vars(SafetyCheckVisitorParams) {
if globals.Contains(v) {
safe.Add(v)
} else {
vis.Clear().WithParams(SafetyCheckVisitorParams).Walk(e)
for v := range vis.Vars() {
if _, ok := safe[v]; !ok {
unsafe.Add(e, v)
}
}
}
reordered := make(Body, 0, len(body))
output := VarSet{}
for {
n := len(reordered)
@@ -3928,15 +4006,16 @@ func reorderBodyForSafety(builtins map[string]*Builtin, arity func(Ref) int, glo
continue
}
ovs := outputVarsForExpr(e, arity, safe)
ovs := outputVarsForExpr(e, arity, safe, output)
// check closures: is this expression closing over variables that
// haven't been made safe by what's already included in `reordered`?
vs := unsafeVarsInClosures(e)
cv := vs.Intersect(bodyVars).Diff(globals)
uv := cv.Diff(outputVarsForBody(reordered, arity, safe))
ob := outputVarsForBody(reordered, arity, safe)
if len(uv) > 0 {
if cv.DiffCount(ob) > 0 {
uv := cv.Diff(ob)
if uv.Equal(ovs) { // special case "closure-self"
continue
}
@@ -3965,18 +4044,22 @@ func reorderBodyForSafety(builtins map[string]*Builtin, arity func(Ref) int, glo
// Update the globals at each expression to include the variables that could
// be closed over.
g := globals.Copy()
xform := &bodySafetyTransformer{
builtins: builtins,
arity: arity,
}
gvis := &GenericVisitor{}
for i, e := range reordered {
if i > 0 {
g.Update(reordered[i-1].Vars(SafetyCheckVisitorParams))
vis.Walk(reordered[i-1])
g.Update(vis.Vars())
vis.Clear().WithParams(SafetyCheckVisitorParams)
}
xform := &bodySafetyTransformer{
builtins: builtins,
arity: arity,
current: e,
globals: g,
unsafe: unsafe,
}
NewGenericVisitor(xform.Visit).Walk(e)
xform.current = e
xform.globals = g
xform.unsafe = unsafe
gvis.f = xform.Visit
gvis.Walk(e)
}
return reordered, unsafe
@@ -4035,9 +4118,12 @@ func (xform *bodySafetyTransformer) Visit(x any) bool {
func (xform *bodySafetyTransformer) reorderComprehensionSafety(tv VarSet, body Body) Body {
bv := body.Vars(SafetyCheckVisitorParams)
bv.Update(xform.globals)
uv := tv.Diff(bv)
for v := range uv {
xform.unsafe.Add(xform.current, v)
if tv.DiffCount(bv) > 0 {
uv := tv.Diff(bv)
for v := range uv {
xform.unsafe.Add(xform.current, v)
}
}
r, u := reorderBodyForSafety(xform.builtins, xform.arity, xform.globals, body)
@@ -4070,7 +4156,7 @@ func unsafeVarsInClosures(e *Expr) VarSet {
WalkClosures(e, func(x any) bool {
vis := &VarVisitor{vars: vs}
if ev, ok := x.(*Every); ok {
vis.Walk(ev.Body)
vis.WalkBody(ev.Body)
return true
}
vis.Walk(x)
@@ -4088,8 +4174,9 @@ func OutputVarsFromBody(c *Compiler, body Body, safe VarSet) VarSet {
func outputVarsForBody(body Body, arity func(Ref) int, safe VarSet) VarSet {
o := safe.Copy()
output := VarSet{}
for _, e := range body {
o.Update(outputVarsForExpr(e, arity, o))
o.Update(outputVarsForExpr(e, arity, o, output))
}
return o.Diff(safe)
}
@@ -4098,23 +4185,22 @@ func outputVarsForBody(body Body, arity func(Ref) int, safe VarSet) VarSet {
// the given expression. For safety checks this means that they would be
// made safe by the expr.
func OutputVarsFromExpr(c *Compiler, expr *Expr, safe VarSet) VarSet {
return outputVarsForExpr(expr, c.GetArity, safe)
return outputVarsForExpr(expr, c.GetArity, safe, VarSet{})
}
func outputVarsForExpr(expr *Expr, arity func(Ref) int, safe VarSet) VarSet {
func outputVarsForExpr(expr *Expr, arity func(Ref) int, safe VarSet, output VarSet) VarSet {
// Negated expressions must be safe.
if expr.Negated {
return VarSet{}
}
var vis *VarVisitor
// With modifier inputs must be safe.
for _, with := range expr.With {
vis := NewVarVisitor().WithParams(SafetyCheckVisitorParams)
vis = vis.ClearOrNew().WithParams(SafetyCheckVisitorParams)
vis.Walk(with)
vars := vis.Vars()
unsafe := vars.Diff(safe)
if len(unsafe) > 0 {
if vis.Vars().DiffCount(safe) > 0 {
return VarSet{}
}
}
@@ -4124,7 +4210,7 @@ func outputVarsForExpr(expr *Expr, arity func(Ref) int, safe VarSet) VarSet {
return outputVarsForTerms(expr, safe)
case []*Term:
if expr.IsEquality() {
return outputVarsForExprEq(expr, safe)
return outputVarsForExprEq(expr, safe, output)
}
operator, ok := terms[0].Value.(Ref)
@@ -4137,7 +4223,7 @@ func outputVarsForExpr(expr *Expr, arity func(Ref) int, safe VarSet) VarSet {
return VarSet{}
}
return outputVarsForExprCall(expr, ar, safe, terms)
return outputVarsForExprCall(expr, ar, safe, terms, vis, output)
case *Every:
return outputVarsForTerms(terms.Domain, safe)
default:
@@ -4145,22 +4231,26 @@ func outputVarsForExpr(expr *Expr, arity func(Ref) int, safe VarSet) VarSet {
}
}
func outputVarsForExprEq(expr *Expr, safe VarSet) VarSet {
func outputVarsForExprEq(expr *Expr, safe VarSet, output VarSet) VarSet {
if !validEqAssignArgCount(expr) {
return safe
}
output := outputVarsForTerms(expr, safe)
output.Update(outputVarsForTerms(expr, safe))
output.Update(safe)
output.Update(Unify(output, expr.Operand(0), expr.Operand(1)))
return output.Diff(safe)
diff := output.Diff(safe)
clear(output)
return diff
}
func outputVarsForExprCall(expr *Expr, arity int, safe VarSet, terms []*Term) VarSet {
func outputVarsForExprCall(expr *Expr, arity int, safe VarSet, terms []*Term, vis *VarVisitor, output VarSet) VarSet {
clear(output)
output := outputVarsForTerms(expr, safe)
output.Update(outputVarsForTerms(expr, safe))
numInputTerms := arity + 1
if numInputTerms >= len(terms) {
@@ -4173,16 +4263,16 @@ func outputVarsForExprCall(expr *Expr, arity int, safe VarSet, terms []*Term) Va
SkipObjectKeys: true,
SkipRefHead: true,
}
vis := NewVarVisitor().WithParams(params)
vis.Walk(Args(terms[:numInputTerms]))
unsafe := vis.Vars().Diff(output).Diff(safe)
vis = vis.ClearOrNew().WithParams(params)
vis.WalkArgs(Args(terms[:numInputTerms]))
if len(unsafe) > 0 {
unsafe := vis.Vars().Diff(output).DiffCount(safe)
if unsafe > 0 {
return VarSet{}
}
vis = NewVarVisitor().WithParams(params)
vis.Walk(Args(terms[numInputTerms:]))
vis = vis.Clear().WithParams(params)
vis.WalkArgs(Args(terms[numInputTerms:]))
output.Update(vis.vars)
return output
}
@@ -4197,8 +4287,13 @@ func outputVarsForTerms(expr any, safe VarSet) VarSet {
if !isRefSafe(r, safe) {
return true
}
output.Update(r.OutputVars())
return false
if !r.IsGround() {
// Avoiding r.OutputVars() here as it won't allow reusing the visitor.
vis := varVisitorPool.Get().WithParams(VarVisitorParams{SkipRefHead: true})
vis.WalkRef(r)
output.Update(vis.Vars())
varVisitorPool.Put(vis)
}
}
return false
})
@@ -4231,19 +4326,17 @@ type localVarGenerator struct {
}
func newLocalVarGeneratorForModuleSet(sorted []string, modules map[string]*Module) *localVarGenerator {
exclude := NewVarSet()
vis := &VarVisitor{vars: exclude}
vis := NewVarVisitor()
for _, key := range sorted {
vis.Walk(modules[key])
}
return &localVarGenerator{exclude: exclude, next: 0}
return &localVarGenerator{exclude: vis.vars, next: 0}
}
func newLocalVarGenerator(suffix string, node any) *localVarGenerator {
exclude := NewVarSet()
vis := &VarVisitor{vars: exclude}
vis := NewVarVisitor()
vis.Walk(node)
return &localVarGenerator{exclude: exclude, suffix: suffix, next: 0}
return &localVarGenerator{exclude: vis.vars, suffix: suffix, next: 0}
}
func (l *localVarGenerator) Generate() Var {
@@ -4257,20 +4350,17 @@ func (l *localVarGenerator) Generate() Var {
}
func getGlobals(pkg *Package, rules []Ref, imports []*Import) map[Var]*usedRef {
globals := make(map[Var]*usedRef, len(rules)+len(imports))
globals := make(map[Var]*usedRef, len(rules)) // NB: might grow bigger with imports
// Populate globals with exports within the package.
for _, ref := range rules {
v := ref[0].Value.(Var)
globals[v] = &usedRef{ref: pkg.Path.Append(StringTerm(string(v)))}
}
// Populate globals with imports.
for _, imp := range imports {
path := imp.Path.Value.(Ref)
if FutureRootDocument.Equal(path[0]) || RegoRootDocument.Equal(path[0]) {
continue // ignore future and rego imports
continue
}
globals[imp.Name()] = &usedRef{ref: path}
}
@@ -4635,8 +4725,6 @@ func rewriteComprehensionTerms(f *equalityFactory, node any) (any, error) {
})
}
var doubleEq = Equal.Ref()
// rewriteEquals will rewrite exprs under x as unification calls instead of ==
// calls. For example:
//
@@ -5055,7 +5143,7 @@ type localDeclaredVars struct {
assignment bool
}
type varOccurrence int
type varOccurrence uint8
const (
newVar varOccurrence = iota
@@ -5067,7 +5155,6 @@ const (
type declaredVarSet struct {
vs map[Var]Var
reverse map[Var]Var
occurrence map[Var]varOccurrence
count map[Var]int
}
@@ -5075,12 +5162,19 @@ type declaredVarSet struct {
func newDeclaredVarSet() *declaredVarSet {
return &declaredVarSet{
vs: map[Var]Var{},
reverse: map[Var]Var{},
occurrence: map[Var]varOccurrence{},
count: map[Var]int{},
}
}
func (s *declaredVarSet) clear() *declaredVarSet {
clear(s.vs)
clear(s.occurrence)
clear(s.count)
return s
}
func newLocalDeclaredVars() *localDeclaredVars {
return &localDeclaredVars{
vars: []*declaredVarSet{newDeclaredVarSet()},
@@ -5088,21 +5182,39 @@ func newLocalDeclaredVars() *localDeclaredVars {
}
}
func (s *localDeclaredVars) Clear() {
var vs *declaredVarSet
if len(s.vars) > 0 {
vs = s.vars[0]
}
clear(s.vars)
clear(s.rewritten)
s.vars = s.vars[:0]
if vs != nil {
s.vars = append(s.vars, vs.clear())
}
if s.vars[0] == nil {
s.vars[0] = newDeclaredVarSet()
}
s.assignment = false
}
func (s *localDeclaredVars) Copy() *localDeclaredVars {
stack := &localDeclaredVars{
vars: []*declaredVarSet{},
rewritten: map[Var]Var{},
vars: make([]*declaredVarSet, 0, len(s.vars)),
}
for i := range s.vars {
stack.vars = append(stack.vars, newDeclaredVarSet())
maps.Copy(stack.vars[0].vs, s.vars[i].vs)
maps.Copy(stack.vars[0].reverse, s.vars[i].reverse)
maps.Copy(stack.vars[0].occurrence, s.vars[i].occurrence)
maps.Copy(stack.vars[0].count, s.vars[i].count)
}
maps.Copy(stack.rewritten, s.rewritten)
stack.rewritten = maps.Clone(s.rewritten)
return stack
}
@@ -5125,7 +5237,6 @@ func (s localDeclaredVars) Peek() *declaredVarSet {
func (s localDeclaredVars) Insert(x, y Var, occurrence varOccurrence) {
elem := s.vars[len(s.vars)-1]
elem.vs[x] = y
elem.reverse[y] = x
elem.occurrence[x] = occurrence
elem.count[x] = 1
@@ -5205,7 +5316,6 @@ func rewriteLocalVars(g *localVarGenerator, stack *localDeclaredVars, used VarSe
}
func rewriteDeclaredVarsInBody(g *localVarGenerator, stack *localDeclaredVars, used VarSet, body Body, errs Errors, strict bool) (Body, Errors) {
var cpy Body
for i := range body {
@@ -5238,12 +5348,22 @@ func rewriteDeclaredVarsInBody(g *localVarGenerator, stack *localDeclaredVars, u
}
func checkUnusedAssignedVars(body Body, stack *localDeclaredVars, used VarSet, errs Errors, strict bool) Errors {
if !strict || len(errs) > 0 {
return errs
}
dvs := stack.Peek()
hasAssignedVars := false
for _, occ := range dvs.occurrence {
if occ == assignedVar {
hasAssignedVars = true
}
}
if !hasAssignedVars {
return errs
}
unused := NewVarSet()
for v, occ := range dvs.occurrence {
@@ -5264,18 +5384,26 @@ func checkUnusedAssignedVars(body Body, stack *localDeclaredVars, used VarSet, e
}
unused = unused.Diff(rewrittenUsed)
if len(unused) == 0 {
return errs
}
reversed := make(map[Var]Var, len(dvs.vs))
for k, v := range dvs.vs {
reversed[v] = k
}
for _, gv := range unused.Sorted() {
found := false
for i := range body {
if body[i].Vars(VarVisitorParams{}).Contains(gv) {
errs = append(errs, NewError(CompileErr, body[i].Loc(), "assigned var %v unused", dvs.reverse[gv]))
errs = append(errs, NewError(CompileErr, body[i].Loc(), "assigned var %v unused", reversed[gv]))
found = true
break
}
}
if !found {
errs = append(errs, NewError(CompileErr, body[0].Loc(), "assigned var %v unused", dvs.reverse[gv]))
errs = append(errs, NewError(CompileErr, body[0].Loc(), "assigned var %v unused", reversed[gv]))
}
}
@@ -5291,6 +5419,17 @@ func checkUnusedDeclaredVars(body Body, stack *localDeclaredVars, used VarSet, c
}
dvs := stack.Peek()
hasDeclaredVars := false
for _, occ := range dvs.occurrence {
if occ == declaredVar {
hasDeclaredVars = true
}
}
if !hasDeclaredVars {
return errs
}
declared := NewVarSet()
for v, occ := range dvs.occurrence {
@@ -5309,27 +5448,35 @@ func checkUnusedDeclaredVars(body Body, stack *localDeclaredVars, used VarSet, c
}
}
unused := declared.Diff(bodyvars).Diff(used)
dbv := declared.Diff(bodyvars)
if dbv.DiffCount(used) == 0 {
return errs
}
for _, gv := range unused.Sorted() {
rv := dvs.reverse[gv]
reversed := make(map[Var]Var, len(dvs.vs))
for k, v := range dvs.vs {
reversed[v] = k
}
for _, gv := range dbv.Diff(used).Sorted() {
rv := reversed[gv]
if !rv.IsGenerated() {
// Scan through body exprs, looking for a match between the
// bad var's original name, and each expr's declared vars.
foundUnusedVarByName := false
for i := range body {
varsDeclaredInExpr := declaredVars(body[i])
if varsDeclaredInExpr.Contains(dvs.reverse[gv]) {
if varsDeclaredInExpr.Contains(rv) {
// TODO(philipc): Clean up the offset logic here when the parser
// reports more accurate locations.
errs = append(errs, NewError(CompileErr, body[i].Loc(), "declared var %v unused", dvs.reverse[gv]))
errs = append(errs, NewError(CompileErr, body[i].Loc(), "declared var %v unused", rv))
foundUnusedVarByName = true
break
}
}
// Default error location returned.
if !foundUnusedVarByName {
errs = append(errs, NewError(CompileErr, body[0].Loc(), "declared var %v unused", dvs.reverse[gv]))
errs = append(errs, NewError(CompileErr, body[0].Loc(), "declared var %v unused", rv))
}
}
}
@@ -5351,7 +5498,7 @@ func rewriteEveryStatement(g *localVarGenerator, stack *localDeclaredVars, expr
if v := every.Key.Value.(Var); !v.IsWildcard() {
gv, err := rewriteDeclaredVar(g, stack, v, declaredVar)
if err != nil {
return nil, append(errs, NewError(CompileErr, every.Loc(), err.Error())) //nolint:govet
return nil, append(errs, NewError(CompileErr, every.Loc(), "%s", err.Error()))
}
every.Key.Value = gv
}
@@ -5363,7 +5510,7 @@ func rewriteEveryStatement(g *localVarGenerator, stack *localDeclaredVars, expr
if v := every.Value.Value.(Var); !v.IsWildcard() {
gv, err := rewriteDeclaredVar(g, stack, v, declaredVar)
if err != nil {
return nil, append(errs, NewError(CompileErr, every.Loc(), err.Error())) //nolint:govet
return nil, append(errs, NewError(CompileErr, every.Loc(), "%s", err.Error()))
}
every.Value.Value = gv
}
@@ -5381,7 +5528,7 @@ func rewriteSomeDeclStatement(g *localVarGenerator, stack *localDeclaredVars, ex
switch v := decl.Symbols[i].Value.(type) {
case Var:
if _, err := rewriteDeclaredVar(g, stack, v, declaredVar); err != nil {
return nil, append(errs, NewError(CompileErr, decl.Loc(), err.Error())) //nolint:govet
return nil, append(errs, NewError(CompileErr, decl.Loc(), "%s", err.Error()))
}
case Call:
var key, val, container *Term
@@ -5407,9 +5554,11 @@ func rewriteSomeDeclStatement(g *localVarGenerator, stack *localDeclaredVars, ex
RefTerm(VarTerm(Equality.Name)), val, rhs,
}
for _, v0 := range outputVarsForExprEq(e, container.Vars()).Sorted() {
output := VarSet{}
for _, v0 := range outputVarsForExprEq(e, container.Vars(), output).Sorted() {
if _, err := rewriteDeclaredVar(g, stack, v0, declaredVar); err != nil {
return nil, append(errs, NewError(CompileErr, decl.Loc(), err.Error())) //nolint:govet
return nil, append(errs, NewError(CompileErr, decl.Loc(), "%s", err.Error()))
}
}
return rewriteDeclaredVarsInExpr(g, stack, e, errs, strict)
@@ -5463,7 +5612,7 @@ func rewriteDeclaredAssignment(g *localVarGenerator, stack *localDeclaredVars, e
switch v := t.Value.(type) {
case Var:
if gv, err := rewriteDeclaredVar(g, stack, v, assignedVar); err != nil {
errs = append(errs, NewError(CompileErr, t.Location, err.Error())) //nolint:govet
errs = append(errs, NewError(CompileErr, t.Location, "%s", err.Error()))
} else {
t.Value = gv
}
@@ -5478,7 +5627,7 @@ func rewriteDeclaredAssignment(g *localVarGenerator, stack *localDeclaredVars, e
case Ref:
if RootDocumentRefs.Contains(t) {
if gv, err := rewriteDeclaredVar(g, stack, v[0].Value.(Var), assignedVar); err != nil {
errs = append(errs, NewError(CompileErr, t.Location, err.Error())) //nolint:govet
errs = append(errs, NewError(CompileErr, t.Location, "%s", err.Error()))
} else {
t.Value = gv
}
@@ -5845,7 +5994,6 @@ func isVirtual(node *TreeNode, ref Ref) bool {
}
func safetyErrorSlice(unsafe unsafeVars, rewritten map[Var]Var) (result Errors) {
if len(unsafe) == 0 {
return
}
@@ -5897,7 +6045,7 @@ func safetyErrorSlice(unsafe unsafeVars, rewritten map[Var]Var) (result Errors)
}
func checkUnsafeBuiltins(unsafeBuiltinsMap map[string]struct{}, node any) Errors {
errs := make(Errors, 0)
var errs Errors
WalkExprs(node, func(x *Expr) bool {
if x.IsCall() {
operator := x.Operator().String()
@@ -0,0 +1,14 @@
// Copyright 2025 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 ast
var defaultModuleLoader ModuleLoader
// DefaultModuleLoader lets you inject an `ast.ModuleLoader` that will
// always be used. If another one is provided with the ast package,
// they will both be consulted to enrich the set of modules dynamically.
func DefaultModuleLoader(ml ModuleLoader) {
defaultModuleLoader = ml
}
+1 -1
View File
@@ -2343,7 +2343,7 @@ func (p *Parser) genwildcard() string {
}
func (p *Parser) error(loc *location.Location, reason string) {
p.errorf(loc, reason) //nolint:govet
p.errorf(loc, "%s", reason)
}
func (p *Parser) errorf(loc *location.Location, f string, a ...any) {
+1 -1
View File
@@ -687,7 +687,7 @@ func parseModule(filename string, stmts []Statement, comments []*Comment, regoCo
case Body:
rule, err := ParseRuleFromBody(mod, stmt)
if err != nil {
errs = append(errs, NewError(ParseErr, stmt[0].Location, err.Error())) //nolint:govet
errs = append(errs, NewError(ParseErr, stmt[0].Location, "%s", err.Error()))
continue
}
rule.generatedBody = true
+7 -7
View File
@@ -1050,10 +1050,10 @@ func (head *Head) MarshalJSON() ([]byte, error) {
// Vars returns a set of vars found in the head.
func (head *Head) Vars() VarSet {
vis := &VarVisitor{vars: VarSet{}}
vis := NewVarVisitor()
// TODO: improve test coverage for this.
if head.Args != nil {
vis.Walk(head.Args)
vis.WalkArgs(head.Args)
}
if head.Key != nil {
vis.Walk(head.Key)
@@ -1062,7 +1062,7 @@ func (head *Head) Vars() VarSet {
vis.Walk(head.Value)
}
if len(head.Reference) > 0 {
vis.Walk(head.Reference[1:])
vis.WalkRef(head.Reference[1:])
}
return vis.vars
}
@@ -1119,8 +1119,8 @@ func (a Args) SetLoc(loc *Location) {
// Vars returns a set of vars that appear in a.
func (a Args) Vars() VarSet {
vis := &VarVisitor{vars: VarSet{}}
vis.Walk(a)
vis := NewVarVisitor()
vis.WalkArgs(a)
return vis.vars
}
@@ -1243,7 +1243,7 @@ func (body Body) String() string {
// control which vars are included.
func (body Body) Vars(params VarVisitorParams) VarSet {
vis := NewVarVisitor().WithParams(params)
vis.Walk(body)
vis.WalkBody(body)
return vis.Vars()
}
@@ -1763,7 +1763,7 @@ func (q *Every) Compare(other *Every) int {
// KeyValueVars returns the key and val arguments of an `every`
// expression, if they are non-nil and not wildcards.
func (q *Every) KeyValueVars() VarSet {
vis := &VarVisitor{vars: VarSet{}}
vis := NewVarVisitor()
if q.Key != nil {
vis.Walk(q.Key)
}
+17
View File
@@ -0,0 +1,17 @@
package ast
import "context"
type regoCompileCtx struct{}
func WithCompiler(ctx context.Context, c *Compiler) context.Context {
return context.WithValue(ctx, regoCompileCtx{}, c)
}
func CompilerFromContext(ctx context.Context) (*Compiler, bool) {
if ctx == nil {
return nil, false
}
v, ok := ctx.Value(regoCompileCtx{}).(*Compiler)
return v, ok
}
+23
View File
@@ -17,6 +17,10 @@ type indexResultPool struct {
pool sync.Pool
}
type vvPool struct {
pool sync.Pool
}
func (p *termPtrPool) Get() *Term {
return p.pool.Get().(*Term)
}
@@ -44,6 +48,17 @@ func (p *indexResultPool) Put(x *IndexResult) {
}
}
func (p *vvPool) Get() *VarVisitor {
return p.pool.Get().(*VarVisitor)
}
func (p *vvPool) Put(vv *VarVisitor) {
if vv != nil {
vv.Clear()
p.pool.Put(vv)
}
}
var TermPtrPool = &termPtrPool{
pool: sync.Pool{
New: func() any {
@@ -60,6 +75,14 @@ var sbPool = &stringBuilderPool{
},
}
var varVisitorPool = &vvPool{
pool: sync.Pool{
New: func() any {
return NewVarVisitor()
},
},
}
var IndexResultPool = &indexResultPool{
pool: sync.Pool{
New: func() any {
+33 -52
View File
@@ -452,7 +452,7 @@ func (term *Term) UnmarshalJSON(bs []byte) error {
// Vars returns a VarSet with variables contained in this term.
func (term *Term) Vars() VarSet {
vis := &VarVisitor{vars: VarSet{}}
vis := NewVarVisitor()
vis.Walk(term)
return vis.vars
}
@@ -674,6 +674,9 @@ func FloatNumberTerm(f float64) *Term {
func (num Number) Equal(other Value) bool {
switch other := other.(type) {
case Number:
if num == other {
return true
}
if n1, ok1 := num.Int64(); ok1 {
n2, ok2 := other.Int64()
if ok1 && ok2 {
@@ -718,6 +721,11 @@ func (num Number) Find(path Ref) (Value, error) {
// Hash returns the hash code for the Value.
func (num Number) Hash() int {
if len(num) < 4 {
if i, err := strconv.Atoi(string(num)); err == nil {
return i
}
}
f, err := json.Number(num).Float64()
if err != nil {
bs := []byte(num)
@@ -1227,7 +1235,7 @@ func (ref Ref) String() string {
// this expression in isolation.
func (ref Ref) OutputVars() VarSet {
vis := NewVarVisitor().WithParams(VarVisitorParams{SkipRefHead: true})
vis.Walk(ref)
vis.WalkRef(ref)
return vis.Vars()
}
@@ -1331,10 +1339,7 @@ func (arr *Array) Find(path Ref) (Value, error) {
return nil, errFindNotFound
}
i, ok := num.Int()
if !ok {
return nil, errFindNotFound
}
if i < 0 || i >= arr.Len() {
if !ok || i < 0 || i >= arr.Len() {
return nil, errFindNotFound
}
@@ -1355,12 +1360,7 @@ func (arr *Array) Get(pos *Term) *Term {
return nil
}
i, ok := num.Int()
if !ok {
return nil
}
if i >= 0 && i < len(arr.elems) {
if i, ok := num.Int(); ok && i >= 0 && i < len(arr.elems) {
return arr.elems[i]
}
@@ -2194,24 +2194,21 @@ func (l *lazyObj) Find(path Ref) (Value, error) {
}
type object struct {
elems map[int]*objectElem
keys objectElemSlice
ground int // number of key and value grounds. Counting is
// required to support insert's key-value replace.
elems map[int]*objectElem
keys []*objectElem
ground int // number of key and value grounds. Counting is required to support insert's key-value replace.
hash int
sortGuard sync.Once // Prevents race condition around sorting.
}
func newobject(n int) *object {
var keys objectElemSlice
var keys []*objectElem
if n > 0 {
keys = make(objectElemSlice, 0, n)
keys = make([]*objectElem, 0, n)
}
return &object{
elems: make(map[int]*objectElem, n),
keys: keys,
ground: 0,
hash: 0,
sortGuard: sync.Once{},
}
}
@@ -2222,19 +2219,13 @@ type objectElem struct {
next *objectElem
}
type objectElemSlice []*objectElem
func (s objectElemSlice) Less(i, j int) bool { return Compare(s[i].key.Value, s[j].key.Value) < 0 }
func (s objectElemSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s objectElemSlice) Len() int { return len(s) }
// Item is a helper for constructing an tuple containing two Terms
// representing a key/value pair in an Object.
func Item(key, value *Term) [2]*Term {
return [2]*Term{key, value}
}
func (obj *object) sortedKeys() objectElemSlice {
func (obj *object) sortedKeys() []*objectElem {
obj.sortGuard.Do(func() {
slices.SortFunc(obj.keys, func(a, b *objectElem) int {
return a.key.Value.Compare(b.key.Value)
@@ -2540,6 +2531,9 @@ func (obj *object) get(k *Term) *objectElem {
case Number:
if xi, ok := x.Int64(); ok {
equal = func(y Value) bool {
if x == y {
return true
}
if y, ok := y.(Number); ok {
if yi, ok := y.Int64(); ok {
return xi == yi
@@ -2630,6 +2624,9 @@ func (obj *object) insert(k, v *Term, resetSortGuard bool) {
case Number:
if xi, err := json.Number(x).Int64(); err == nil {
equal = func(y Value) bool {
if x == y {
return true
}
if y, ok := y.(Number); ok {
if yi, err := json.Number(y).Int64(); err == nil {
return xi == yi
@@ -2697,10 +2694,6 @@ func (obj *object) insert(k, v *Term, resetSortGuard bool) {
for curr := head; curr != nil; curr = curr.next {
if equal(curr.key.Value) {
// The ground bit of the value may change in
// replace, hence adjust the counter per old
// and new value.
if curr.value.IsGround() {
obj.ground--
}
@@ -2708,20 +2701,21 @@ func (obj *object) insert(k, v *Term, resetSortGuard bool) {
obj.ground++
}
// Update hash based on the new value
curr.value = v
obj.elems[hash] = curr
obj.hash = 0
for ehash := range obj.elems {
obj.hash += ehash + obj.elems[ehash].value.Hash()
}
obj.rehash()
return
}
}
elem := &objectElem{
key: k,
value: v,
next: head,
}
obj.elems[hash] = elem
obj.elems[hash] = &objectElem{key: k, value: v, next: head}
// O(1) insertion, but we'll have to re-sort the keys later.
obj.keys = append(obj.keys, elem)
obj.keys = append(obj.keys, obj.elems[hash])
if resetSortGuard {
// Reset the sync.Once instance.
@@ -2742,19 +2736,6 @@ func (obj *object) insert(k, v *Term, resetSortGuard bool) {
}
}
func (obj *object) rehash() {
// obj.keys is considered truth, from which obj.hash and obj.elems are recalculated.
obj.hash = 0
obj.elems = make(map[int]*objectElem, len(obj.keys))
for _, elem := range obj.keys {
hash := elem.key.Hash()
obj.hash += hash + elem.value.Hash()
obj.elems[hash] = elem
}
}
func filterObject(o Value, filter Value) (Value, error) {
if (Null{}).Equal(filter) {
return o, nil
+20 -15
View File
@@ -21,10 +21,12 @@ func isRefSafe(ref Ref, safe VarSet) bool {
}
func isCallSafe(call Call, safe VarSet) bool {
vis := NewVarVisitor().WithParams(SafetyCheckVisitorParams)
vis := varVisitorPool.Get().WithParams(SafetyCheckVisitorParams)
vis.Walk(call)
unsafe := vis.Vars().Diff(safe)
return len(unsafe) == 0
isSafe := vis.Vars().DiffCount(safe) == 0
varVisitorPool.Put(vis)
return isSafe
}
// Unify returns a set of variables that will be unified when the equality expression defined by
@@ -173,11 +175,16 @@ func (u *unifier) unify(a *Term, b *Term) {
}
func (u *unifier) markAllSafe(x Value) {
vis := u.varVisitor()
vis := varVisitorPool.Get().WithParams(VarVisitorParams{
SkipRefHead: true,
SkipObjectKeys: true,
SkipClosures: true,
})
vis.Walk(x)
for v := range vis.Vars() {
u.markSafe(v)
}
varVisitorPool.Put(vis)
}
func (u *unifier) markSafe(x Var) {
@@ -204,16 +211,21 @@ func (u *unifier) markSafe(x Var) {
func (u *unifier) markUnknown(a, b Var) {
if _, ok := u.unknown[a]; !ok {
u.unknown[a] = NewVarSet()
u.unknown[a] = NewVarSet(b)
} else {
u.unknown[a].Add(b)
}
u.unknown[a].Add(b)
}
func (u *unifier) unifyAll(a Var, b Value) {
if u.isSafe(a) {
u.markAllSafe(b)
} else {
vis := u.varVisitor()
vis := varVisitorPool.Get().WithParams(VarVisitorParams{
SkipRefHead: true,
SkipObjectKeys: true,
SkipClosures: true,
})
vis.Walk(b)
unsafe := vis.Vars().Diff(u.safe).Diff(u.unified)
if len(unsafe) == 0 {
@@ -223,13 +235,6 @@ func (u *unifier) unifyAll(a Var, b Value) {
u.markUnknown(a, v)
}
}
varVisitorPool.Put(vis)
}
}
func (*unifier) varVisitor() *VarVisitor {
return NewVarVisitor().WithParams(VarVisitorParams{
SkipRefHead: true,
SkipObjectKeys: true,
SkipClosures: true,
})
}
+11 -7
View File
@@ -50,13 +50,7 @@ func (s VarSet) Copy() VarSet {
// Diff returns a VarSet containing variables in s that are not in vs.
func (s VarSet) Diff(vs VarSet) VarSet {
i := 0
for v := range s {
if !vs.Contains(v) {
i++
}
}
r := NewVarSetOfSize(i)
r := NewVarSetOfSize(s.DiffCount(vs))
for v := range s {
if !vs.Contains(v) {
r.Add(v)
@@ -65,6 +59,16 @@ func (s VarSet) Diff(vs VarSet) VarSet {
return r
}
// DiffCount returns the number of variables in s that are not in vs.
func (s VarSet) DiffCount(vs VarSet) (i int) {
for v := range s {
if !vs.Contains(v) {
i++
}
}
return
}
// Equal returns true if s contains exactly the same elements as vs.
func (s VarSet) Equal(vs VarSet) bool {
if len(s) != len(vs) {
+7
View File
@@ -539,6 +539,13 @@
"PreRelease": "",
"Metadata": ""
},
"io.jwt.verify_eddsa": {
"Major": 1,
"Minor": 8,
"Patch": 0,
"PreRelease": "",
"Metadata": ""
},
"io.jwt.verify_es256": {
"Major": 0,
"Minor": 17,
+77 -28
View File
@@ -563,12 +563,37 @@ func NewVarVisitor() *VarVisitor {
}
}
// Clear resets the visitor to its initial state, and returns it for chaining.
func (vis *VarVisitor) Clear() *VarVisitor {
vis.params = VarVisitorParams{}
clear(vis.vars)
return vis
}
// ClearOrNew returns a new VarVisitor if vis is nil, or else a cleared VarVisitor.
func (vis *VarVisitor) ClearOrNew() *VarVisitor {
if vis == nil {
return NewVarVisitor()
}
return vis.Clear()
}
// WithParams sets the parameters in params on vis.
func (vis *VarVisitor) WithParams(params VarVisitorParams) *VarVisitor {
vis.params = params
return vis
}
// Add adds a variable v to the visitor's set of variables.
func (vis *VarVisitor) Add(v Var) {
if vis.vars == nil {
vis.vars = NewVarSet(v)
} else {
vis.vars.Add(v)
}
}
// Vars returns a VarSet that contains collected vars.
func (vis *VarVisitor) Vars() VarSet {
return vis.vars
@@ -661,7 +686,7 @@ func (vis *VarVisitor) visit(v any) bool {
}
}
if v, ok := v.(Var); ok {
vis.vars.Add(v)
vis.Add(v)
}
return false
}
@@ -687,58 +712,55 @@ func (vis *VarVisitor) Walk(x any) {
vis.Walk(x.Comments[i])
}
case *Package:
vis.Walk(x.Path)
vis.WalkRef(x.Path)
case *Import:
vis.Walk(x.Path)
vis.Walk(x.Alias)
if x.Alias != "" {
vis.Add(x.Alias)
}
case *Rule:
vis.Walk(x.Head)
vis.Walk(x.Body)
vis.WalkBody(x.Body)
if x.Else != nil {
vis.Walk(x.Else)
}
case *Head:
if len(x.Reference) > 0 {
vis.Walk(x.Reference)
vis.WalkRef(x.Reference)
} else {
vis.Walk(x.Name)
vis.Add(x.Name)
if x.Key != nil {
vis.Walk(x.Key)
}
}
vis.Walk(x.Args)
vis.WalkArgs(x.Args)
if x.Value != nil {
vis.Walk(x.Value)
}
case Body:
for i := range x {
vis.Walk(x[i])
}
vis.WalkBody(x)
case Args:
for i := range x {
vis.Walk(x[i])
}
vis.WalkArgs(x)
case *Expr:
switch ts := x.Terms.(type) {
case *Term, *SomeDecl, *Every:
vis.Walk(ts)
case []*Term:
for i := range ts {
vis.Walk(ts[i])
vis.Walk(ts[i].Value)
}
}
for i := range x.With {
vis.Walk(x.With[i])
}
case *With:
vis.Walk(x.Target)
vis.Walk(x.Value)
vis.Walk(x.Target.Value)
vis.Walk(x.Value.Value)
case *Term:
vis.Walk(x.Value)
case Ref:
for i := range x {
vis.Walk(x[i])
vis.Walk(x[i].Value)
}
case *object:
x.Foreach(func(k, _ *Term) {
@@ -755,29 +777,56 @@ func (vis *VarVisitor) Walk(x any) {
vis.Walk(xSlice[i])
}
case *ArrayComprehension:
vis.Walk(x.Term)
vis.Walk(x.Body)
vis.Walk(x.Term.Value)
vis.WalkBody(x.Body)
case *ObjectComprehension:
vis.Walk(x.Key)
vis.Walk(x.Value)
vis.Walk(x.Body)
vis.Walk(x.Key.Value)
vis.Walk(x.Value.Value)
vis.WalkBody(x.Body)
case *SetComprehension:
vis.Walk(x.Term)
vis.Walk(x.Body)
vis.Walk(x.Term.Value)
vis.WalkBody(x.Body)
case Call:
for i := range x {
vis.Walk(x[i])
vis.Walk(x[i].Value)
}
case *Every:
if x.Key != nil {
vis.Walk(x.Key)
vis.Walk(x.Key.Value)
}
vis.Walk(x.Value)
vis.Walk(x.Domain)
vis.Walk(x.Body)
vis.WalkBody(x.Body)
case *SomeDecl:
for i := range x.Symbols {
vis.Walk(x.Symbols[i])
}
}
}
// WalkArgs exists only to avoid the allocation cost of boxing Args to `any` in the VarVisitor.
// Use it when you know beforehand that the type to walk is Args.
func (vis *VarVisitor) WalkArgs(x Args) {
for i := range x {
vis.Walk(x[i].Value)
}
}
// WalkRef exists only to avoid the allocation cost of boxing Ref to `any` in the VarVisitor.
// Use it when you know beforehand that the type to walk is a Ref.
func (vis *VarVisitor) WalkRef(ref Ref) {
if vis.params.SkipRefHead {
ref = ref[1:]
}
for _, term := range ref {
vis.Walk(term.Value)
}
}
// WalkBody exists only to avoid the allocation cost of boxing Body to `any` in the VarVisitor.
// Use it when you know beforehand that the type to walk is a Body.
func (vis *VarVisitor) WalkBody(body Body) {
for _, expr := range body {
vis.Walk(expr)
}
}
+47 -5
View File
@@ -21,6 +21,7 @@ import (
"path/filepath"
"reflect"
"strings"
"sync"
"github.com/gobwas/glob"
"github.com/open-policy-agent/opa/internal/file/archive"
@@ -29,6 +30,7 @@ import (
astJSON "github.com/open-policy-agent/opa/v1/ast/json"
"github.com/open-policy-agent/opa/v1/format"
"github.com/open-policy-agent/opa/v1/metrics"
"github.com/open-policy-agent/opa/v1/storage"
"github.com/open-policy-agent/opa/v1/util"
)
@@ -435,6 +437,45 @@ type PlanModuleFile struct {
Raw []byte
}
var (
pluginMtx sync.Mutex
// The bundle activator to use by default.
bundleExtActivator string
// The function to use for creating a storage.Store for bundles.
BundleExtStore func() storage.Store
)
// RegisterDefaultBundleActivator sets the default bundle activator for OPA to use for bundle activation.
// The id must already have been registered with RegisterActivator.
func RegisterDefaultBundleActivator(id string) {
pluginMtx.Lock()
defer pluginMtx.Unlock()
bundleExtActivator = id
}
// RegisterStoreFunc sets the function to use for creating storage for bundles
// in OPA. If no function is registered, OPA will use situational defaults to
// decide on what sort of storage.Store to create when bundle storage is
// needed. Typically the default is inmem.Store.
func RegisterStoreFunc(s func() storage.Store) {
pluginMtx.Lock()
defer pluginMtx.Unlock()
BundleExtStore = s
}
// HasExtension returns true if a default bundle activator has been set
// with RegisterDefaultBundleActivator.
func HasExtension() bool {
pluginMtx.Lock()
defer pluginMtx.Unlock()
return bundleExtActivator != ""
}
// Reader contains the reader to load the bundle from.
type Reader struct {
loader DirectoryLoader
@@ -464,10 +505,11 @@ func NewReader(r io.Reader) *Reader {
// specified DirectoryLoader.
func NewCustomReader(loader DirectoryLoader) *Reader {
nr := Reader{
loader: loader,
metrics: metrics.New(),
files: make(map[string]FileInfo),
sizeLimitBytes: DefaultSizeLimitBytes + 1,
loader: loader,
metrics: metrics.New(),
files: make(map[string]FileInfo),
sizeLimitBytes: DefaultSizeLimitBytes + 1,
lazyLoadingMode: HasExtension(),
}
return &nr
}
@@ -721,7 +763,7 @@ func (r *Reader) Read() (Bundle, error) {
modulePopts.RegoVersion = regoVersion
}
r.metrics.Timer(metrics.RegoModuleParse).Start()
mf.Parsed, err = ast.ParseModuleWithOpts(mf.Path, string(mf.Raw), modulePopts)
mf.Parsed, err = ast.ParseModuleWithOpts(mf.Path, util.ByteSliceToString(mf.Raw), modulePopts)
r.metrics.Timer(metrics.RegoModuleParse).Stop()
if err != nil {
return bundle, err
+44 -15
View File
@@ -6,12 +6,14 @@
package bundle
import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
"strings"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
"github.com/open-policy-agent/opa/internal/jwx/jws/sign"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/open-policy-agent/opa/v1/keys"
"github.com/open-policy-agent/opa/v1/util"
@@ -106,26 +108,53 @@ func (s *SigningConfig) WithPlugin(plugin string) *SigningConfig {
// GetPrivateKey returns the private key or secret from the signing config
func (s *SigningConfig) GetPrivateKey() (any, error) {
var keyData string
block, _ := pem.Decode([]byte(s.Key))
if block != nil {
return sign.GetSigningKey(s.Key, jwa.SignatureAlgorithm(s.Algorithm))
alg, ok := jwa.LookupSignatureAlgorithm(s.Algorithm)
if !ok {
return nil, fmt.Errorf("unknown signature algorithm: %s", s.Algorithm)
}
var priv string
if _, err := os.Stat(s.Key); err == nil {
bs, err := os.ReadFile(s.Key)
if err != nil {
// Check if the key looks like PEM data first (starts with -----BEGIN)
if strings.HasPrefix(s.Key, "-----BEGIN") {
keyData = s.Key
} else {
// Try to read as a file path
if _, err := os.Stat(s.Key); err == nil {
bs, err := os.ReadFile(s.Key)
if err != nil {
return nil, err
}
keyData = string(bs)
} else if os.IsNotExist(err) {
// Not a file, treat as raw key data
keyData = s.Key
} else {
return nil, err
}
priv = string(bs)
} else if os.IsNotExist(err) {
priv = s.Key
} else {
return nil, err
}
return sign.GetSigningKey(priv, jwa.SignatureAlgorithm(s.Algorithm))
// For HMAC algorithms, return the key as bytes
if alg == jwa.HS256() || alg == jwa.HS384() || alg == jwa.HS512() {
return []byte(keyData), nil
}
// For RSA/ECDSA algorithms, parse the PEM-encoded key
block, _ := pem.Decode([]byte(keyData))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
switch block.Type {
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(block.Bytes)
case "PRIVATE KEY":
return x509.ParsePKCS8PrivateKey(block.Bytes)
case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(block.Bytes)
default:
return nil, fmt.Errorf("unsupported key type: %s", block.Type)
}
}
// GetClaims returns the claims by reading the file specified in the signing config
+29 -31
View File
@@ -6,13 +6,11 @@
package bundle
import (
"crypto/rand"
"encoding/json"
"fmt"
"maps"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
"github.com/open-policy-agent/opa/internal/jwx/jws"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwk"
"github.com/lestrrat-go/jwx/v3/jwt"
)
const defaultSignerID = "_default"
@@ -51,7 +49,7 @@ type DefaultSigner struct{}
// included in the payload and the bundle signing config. The keyID if non-empty,
// represents the value for the "keyid" claim in the token
func (*DefaultSigner) GenerateSignedToken(files []FileInfo, sc *SigningConfig, keyID string) (string, error) {
payload, err := generatePayload(files, sc, keyID)
token, err := generateToken(files, sc, keyID)
if err != nil {
return "", err
}
@@ -61,37 +59,35 @@ func (*DefaultSigner) GenerateSignedToken(files []FileInfo, sc *SigningConfig, k
return "", err
}
var headers jws.StandardHeaders
if err := headers.Set(jws.AlgorithmKey, jwa.SignatureAlgorithm(sc.Algorithm)); err != nil {
return "", err
// Parse the algorithm string to jwa.SignatureAlgorithm
alg, ok := jwa.LookupSignatureAlgorithm(sc.Algorithm)
if !ok {
return "", fmt.Errorf("unknown signature algorithm: %s", sc.Algorithm)
}
if keyID != "" {
if err := headers.Set(jws.KeyIDKey, keyID); err != nil {
return "", err
}
// In order to sign the token with a kid, we need a key ID _on_ the key
// (note: we might be able to make this more efficient if we just load
// the key as a JWK from the start)
jwkKey, err := jwk.Import(privateKey)
if err != nil {
return "", fmt.Errorf("failed to import private key: %w", err)
}
if err := jwkKey.Set(jwk.KeyIDKey, keyID); err != nil {
return "", fmt.Errorf("failed to set key ID on JWK: %w", err)
}
hdr, err := json.Marshal(headers)
// Since v3.0.6, jwx will take the fast path for signing the token if
// there's exactly one WithKey in the options with no sub-options
signed, err := jwt.Sign(token, jwt.WithKey(alg, jwkKey))
if err != nil {
return "", err
}
token, err := jws.SignLiteral(payload,
jwa.SignatureAlgorithm(sc.Algorithm),
privateKey,
hdr,
rand.Reader)
if err != nil {
return "", err
}
return string(token), nil
return string(signed), nil
}
func generatePayload(files []FileInfo, sc *SigningConfig, keyID string) ([]byte, error) {
payload := make(map[string]any)
payload["files"] = files
func generateToken(files []FileInfo, sc *SigningConfig, keyID string) (jwt.Token, error) {
tb := jwt.NewBuilder()
tb.Claim("files", files)
if sc.ClaimsPath != "" {
claims, err := sc.GetClaims()
@@ -99,12 +95,14 @@ func generatePayload(files []FileInfo, sc *SigningConfig, keyID string) ([]byte,
return nil, err
}
maps.Copy(payload, claims)
for k, v := range claims {
tb.Claim(k, v)
}
} else if keyID != "" {
// keyid claim is deprecated but include it for backwards compatibility.
payload["keyid"] = keyID
tb.Claim("keyid", keyID)
}
return json.Marshal(payload)
return tb.Build()
}
// GetSigner returns the Signer registered under the given id
+111 -17
View File
@@ -12,7 +12,10 @@ import (
"fmt"
"maps"
"path/filepath"
"slices"
"sort"
"strings"
"sync"
iCompiler "github.com/open-policy-agent/opa/internal/compiler"
"github.com/open-policy-agent/opa/internal/json/patch"
@@ -22,6 +25,15 @@ import (
"github.com/open-policy-agent/opa/v1/util"
)
const defaultActivatorID = "_default"
var (
activators = map[string]Activator{
defaultActivatorID: &DefaultActivator{},
}
activatorMtx sync.Mutex
)
// BundlesBasePath is the storage path used for storing bundle metadata
var BundlesBasePath = storage.MustParsePath("/system/bundles")
@@ -328,6 +340,11 @@ func readEtagFromStore(ctx context.Context, store storage.Store, txn storage.Tra
return str, nil
}
// Activator is the interface expected for implementations that activate bundles.
type Activator interface {
Activate(*ActivateOpts) error
}
// ActivateOpts defines options for the Activate API call.
type ActivateOpts struct {
Ctx context.Context
@@ -340,15 +357,39 @@ type ActivateOpts struct {
ExtraModules map[string]*ast.Module // Optional
AuthorizationDecisionRef ast.Ref
ParserOptions ast.ParserOptions
Plugin string
legacy bool
}
type DefaultActivator struct{}
func (*DefaultActivator) Activate(opts *ActivateOpts) error {
opts.legacy = false
return activateBundles(opts)
}
// Activate the bundle(s) by loading into the given Store. This will load policies, data, and record
// the manifest in storage. The compiler provided will have had the polices compiled on it.
func Activate(opts *ActivateOpts) error {
opts.legacy = false
return activateBundles(opts)
plugin := opts.Plugin
// For backwards compatibility, check if there is no plugin specified, and use default.
if plugin == "" {
// Invoke extension activator if supplied. Otherwise, use default.
if HasExtension() {
plugin = bundleExtActivator
} else {
plugin = defaultActivatorID
}
}
activator, err := GetActivator(plugin)
if err != nil {
return err
}
return activator.Activate(opts)
}
// DeactivateOpts defines options for the Deactivate API call
@@ -1020,32 +1061,40 @@ func lookup(path storage.Path, data map[string]any) (any, bool) {
return value, ok
}
func hasRootsOverlap(ctx context.Context, store storage.Store, txn storage.Transaction, bundles map[string]*Bundle) error {
collisions := map[string][]string{}
allBundles, err := ReadBundleNamesFromStore(ctx, store, txn)
func hasRootsOverlap(ctx context.Context, store storage.Store, txn storage.Transaction, newBundles map[string]*Bundle) error {
storeBundles, err := ReadBundleNamesFromStore(ctx, store, txn)
if suppressNotFound(err) != nil {
return err
}
allRoots := map[string][]string{}
bundlesWithEmptyRoots := map[string]bool{}
// Build a map of roots for existing bundles already in the system
for _, name := range allBundles {
for _, name := range storeBundles {
roots, err := ReadBundleRootsFromStore(ctx, store, txn, name)
if suppressNotFound(err) != nil {
return err
}
allRoots[name] = roots
if slices.Contains(roots, "") {
bundlesWithEmptyRoots[name] = true
}
}
// Add in any bundles that are being activated, overwrite existing roots
// with new ones where bundles are in both groups.
for name, bundle := range bundles {
for name, bundle := range newBundles {
allRoots[name] = *bundle.Manifest.Roots
if slices.Contains(*bundle.Manifest.Roots, "") {
bundlesWithEmptyRoots[name] = true
}
}
// Now check for each new bundle if it conflicts with any of the others
for name, bundle := range bundles {
collidingBundles := map[string]bool{}
conflictSet := map[string]bool{}
for name, bundle := range newBundles {
for otherBundle, otherRoots := range allRoots {
if name == otherBundle {
// Skip the current bundle being checked
@@ -1055,22 +1104,41 @@ func hasRootsOverlap(ctx context.Context, store storage.Store, txn storage.Trans
// Compare the "new" roots with other existing (or a different bundles new roots)
for _, newRoot := range *bundle.Manifest.Roots {
for _, otherRoot := range otherRoots {
if RootPathsOverlap(newRoot, otherRoot) {
collisions[otherBundle] = append(collisions[otherBundle], newRoot)
if !RootPathsOverlap(newRoot, otherRoot) {
continue
}
collidingBundles[name] = true
collidingBundles[otherBundle] = true
// Different message required if the roots are same
if newRoot == otherRoot {
conflictSet[fmt.Sprintf("root %s is in multiple bundles", newRoot)] = true
} else {
paths := []string{newRoot, otherRoot}
sort.Strings(paths)
conflictSet[fmt.Sprintf("%s overlaps %s", paths[0], paths[1])] = true
}
}
}
}
}
if len(collisions) > 0 {
var bundleNames []string
for name := range collisions {
bundleNames = append(bundleNames, name)
}
return fmt.Errorf("detected overlapping roots in bundle manifest with: %s", bundleNames)
if len(collidingBundles) == 0 {
return nil
}
return nil
bundleNames := strings.Join(util.KeysSorted(collidingBundles), ", ")
if len(bundlesWithEmptyRoots) > 0 {
return fmt.Errorf(
"bundles [%s] have overlapping roots and cannot be activated simultaneously because bundle(s) [%s] specify empty root paths ('') which overlap with any other bundle root",
bundleNames,
strings.Join(util.KeysSorted(bundlesWithEmptyRoots), ", "),
)
}
return fmt.Errorf("detected overlapping roots in manifests for these bundles: [%s] (%s)", bundleNames, strings.Join(util.KeysSorted(conflictSet), ", "))
}
func applyPatches(ctx context.Context, store storage.Store, txn storage.Transaction, patches []PatchOperation) error {
@@ -1149,3 +1217,29 @@ func ActivateLegacy(opts *ActivateOpts) error {
opts.legacy = true
return activateBundles(opts)
}
// GetActivator returns the Activator registered under the given id
func GetActivator(id string) (Activator, error) {
activator, ok := activators[id]
if !ok {
return nil, fmt.Errorf("no activator exists under id %s", id)
}
return activator, nil
}
// RegisterActivator registers a bundle Activator under the given id.
// The id value can later be referenced in ActivateOpts.Plugin to specify
// which activator should be used for that bundle activation operation.
// Note: This must be called *before* RegisterDefaultBundleActivator.
func RegisterActivator(id string, a Activator) {
activatorMtx.Lock()
defer activatorMtx.Unlock()
if id == defaultActivatorID {
panic("cannot use reserved activator id, use a different id")
}
activators[id] = a
}
+89 -31
View File
@@ -7,18 +7,52 @@ package bundle
import (
"bytes"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
"github.com/open-policy-agent/opa/internal/jwx/jws"
"github.com/open-policy-agent/opa/internal/jwx/jws/verify"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jws/jwsbb"
"github.com/open-policy-agent/opa/v1/util"
)
// parseVerificationKey converts a string key to the appropriate type for jws.Verify
func parseVerificationKey(keyData string, alg jwa.SignatureAlgorithm) (any, error) {
// For HMAC algorithms, return the key as bytes
if alg == jwa.HS256() || alg == jwa.HS384() || alg == jwa.HS512() {
return []byte(keyData), nil
}
// For RSA/ECDSA algorithms, try to parse as PEM first
block, _ := pem.Decode([]byte(keyData))
if block != nil {
switch block.Type {
case "RSA PUBLIC KEY":
return x509.ParsePKCS1PublicKey(block.Bytes)
case "PUBLIC KEY":
return x509.ParsePKIXPublicKey(block.Bytes)
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(block.Bytes)
case "PRIVATE KEY":
return x509.ParsePKCS8PrivateKey(block.Bytes)
case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(block.Bytes)
case "CERTIFICATE":
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
return cert.PublicKey, nil
}
}
return nil, errors.New("failed to parse PEM block containing the key")
}
const defaultVerifierID = "_default"
var verifiers map[string]Verifier
@@ -82,26 +116,42 @@ func (*DefaultVerifier) VerifyBundleSignature(sc SignaturesConfig, bvc *Verifica
}
func verifyJWTSignature(token string, bvc *VerificationConfig) (*DecodedSignature, error) {
// decode JWT to check if the header specifies the key to use and/or if claims have the scope.
parts, err := jws.SplitCompact(token)
tokbytes := []byte(token)
hdrb64, payloadb64, signatureb64, err := jwsbb.SplitCompact(tokbytes)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to split compact JWT: %w", err)
}
var decodedHeader []byte
if decodedHeader, err = base64.RawURLEncoding.DecodeString(parts[0]); err != nil {
return nil, fmt.Errorf("failed to base64 decode JWT headers: %w", err)
// check for the id of the key to use for JWT signature verification
// first in the OPA config. If not found, then check the JWT kid.
keyID := bvc.KeyID
if keyID == "" {
// Use jwsbb.Header to access into the "kid" header field, which we will
// use to determine the key to use for verification.
hdr := jwsbb.HeaderParseCompact(hdrb64)
v, err := jwsbb.HeaderGetString(hdr, "kid")
switch {
case err == nil:
// err == nils means we found the key ID in the header
keyID = v
case errors.Is(err, jwsbb.ErrHeaderNotFound()):
// no "kid" in the header. no op.
default:
// some other error occurred while trying to extract the key ID
return nil, fmt.Errorf("failed to extract key ID from headers: %w", err)
}
}
var hdr jws.StandardHeaders
if err := json.Unmarshal(decodedHeader, &hdr); err != nil {
return nil, fmt.Errorf("failed to parse JWT headers: %w", err)
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, err
// Because we want to fallback to ds.KeyID when we can't find the
// keyID, we need to parse the payload here already.
//
// (lestrrat) Whoa, you're going to trust the payload before you
// verify the signature? Even if it's for backwrds compatibility,
// Is this OK?
decoder := base64.RawURLEncoding
payload := make([]byte, decoder.DecodedLen(len(payloadb64)))
if _, err := decoder.Decode(payload, payloadb64); err != nil {
return nil, fmt.Errorf("failed to base64 decode JWT payload: %w", err)
}
var ds DecodedSignature
@@ -109,17 +159,12 @@ func verifyJWTSignature(token string, bvc *VerificationConfig) (*DecodedSignatur
return nil, err
}
// check for the id of the key to use for JWT signature verification
// first in the OPA config. If not found, then check the JWT kid.
keyID := bvc.KeyID
// If header has no key id, check the deprecated key claim.
if keyID == "" {
keyID = hdr.KeyID
}
if keyID == "" {
// If header has no key id, check the deprecated key claim.
keyID = ds.KeyID
}
// If we still don't have a keyID, we cannot proceed
if keyID == "" {
return nil, errors.New("verification key ID is empty")
}
@@ -130,16 +175,29 @@ func verifyJWTSignature(token string, bvc *VerificationConfig) (*DecodedSignatur
return nil, err
}
// verify JWT signature
alg := jwa.SignatureAlgorithm(keyConfig.Algorithm)
key, err := verify.GetSigningKey(keyConfig.Key, alg)
alg, ok := jwa.LookupSignatureAlgorithm(keyConfig.Algorithm)
if !ok {
return nil, fmt.Errorf("unknown signature algorithm: %s", keyConfig.Algorithm)
}
// Parse the key into the appropriate type
parsedKey, err := parseVerificationKey(keyConfig.Key, alg)
if err != nil {
return nil, err
}
_, err = jws.Verify([]byte(token), alg, key)
if err != nil {
return nil, err
signature := make([]byte, decoder.DecodedLen(len(signatureb64)))
if _, err = decoder.Decode(signature, signatureb64); err != nil {
return nil, fmt.Errorf("failed to base64 decode JWT signature: %w", err)
}
signbuf := make([]byte, len(hdrb64)+1+len(payloadb64))
copy(signbuf, hdrb64)
signbuf[len(hdrb64)] = '.'
copy(signbuf[len(hdrb64)+1:], payloadb64)
if err := jwsbb.Verify(parsedKey, alg.String(), signbuf, signature); err != nil {
return nil, fmt.Errorf("failed to verify JWT signature: %w", err)
}
// verify the scope
+174 -41
View File
@@ -9,6 +9,7 @@ import (
"encoding/json"
"errors"
"fmt"
"maps"
"os"
"path/filepath"
"reflect"
@@ -21,6 +22,59 @@ import (
"github.com/open-policy-agent/opa/v1/version"
)
// ServerConfig represents the different server configuration options.
type ServerConfig struct {
Metrics json.RawMessage `json:"metrics,omitempty"`
Encoding json.RawMessage `json:"encoding,omitempty"`
Decoding json.RawMessage `json:"decoding,omitempty"`
}
// Clone creates a deep copy of ServerConfig.
func (s *ServerConfig) Clone() *ServerConfig {
if s == nil {
return nil
}
clone := &ServerConfig{}
if s.Encoding != nil {
clone.Encoding = make(json.RawMessage, len(s.Encoding))
copy(clone.Encoding, s.Encoding)
}
if s.Decoding != nil {
clone.Decoding = make(json.RawMessage, len(s.Decoding))
copy(clone.Decoding, s.Decoding)
}
if s.Metrics != nil {
clone.Metrics = make(json.RawMessage, len(s.Metrics))
copy(clone.Metrics, s.Metrics)
}
return clone
}
// StorageConfig represents Config's storage options.
type StorageConfig struct {
Disk json.RawMessage `json:"disk,omitempty"`
}
// Clone creates a deep copy of StorageConfig.
func (s *StorageConfig) Clone() *StorageConfig {
if s == nil {
return nil
}
clone := &StorageConfig{}
if s.Disk != nil {
clone.Disk = make(json.RawMessage, len(s.Disk))
copy(clone.Disk, s.Disk)
}
return clone
}
// Config represents the configuration file that OPA can be started with.
type Config struct {
Services json.RawMessage `json:"services,omitempty"`
@@ -38,15 +92,9 @@ type Config struct {
NDBuiltinCache bool `json:"nd_builtin_cache,omitempty"`
PersistenceDirectory *string `json:"persistence_directory,omitempty"`
DistributedTracing json.RawMessage `json:"distributed_tracing,omitempty"`
Server *struct {
Encoding json.RawMessage `json:"encoding,omitempty"`
Decoding json.RawMessage `json:"decoding,omitempty"`
Metrics json.RawMessage `json:"metrics,omitempty"`
} `json:"server,omitempty"`
Storage *struct {
Disk json.RawMessage `json:"disk,omitempty"`
} `json:"storage,omitempty"`
Extra map[string]json.RawMessage `json:"-"`
Server *ServerConfig `json:"server,omitempty"`
Storage *StorageConfig `json:"storage,omitempty"`
Extra map[string]json.RawMessage `json:"-"`
}
// ParseConfig returns a valid Config object with defaults injected. The id
@@ -122,38 +170,6 @@ func (c Config) NDBuiltinCacheEnabled() bool {
return c.NDBuiltinCache
}
func (c *Config) validateAndInjectDefaults(id string) error {
if c.DefaultDecision == nil {
s := defaultDecisionPath
c.DefaultDecision = &s
}
_, err := ref.ParseDataPath(*c.DefaultDecision)
if err != nil {
return err
}
if c.DefaultAuthorizationDecision == nil {
s := defaultAuthorizationDecisionPath
c.DefaultAuthorizationDecision = &s
}
_, err = ref.ParseDataPath(*c.DefaultAuthorizationDecision)
if err != nil {
return err
}
if c.Labels == nil {
c.Labels = map[string]string{}
}
c.Labels["id"] = id
c.Labels["version"] = version.Version
return nil
}
// GetPersistenceDirectory returns the configured persistence directory, or $PWD/.opa if none is configured
func (c Config) GetPersistenceDirectory() (string, error) {
if c.PersistenceDirectory == nil {
@@ -197,6 +213,123 @@ func (c *Config) ActiveConfig() (any, error) {
return result, nil
}
// Clone creates a deep copy of the Config struct
func (c *Config) Clone() *Config {
if c == nil {
return nil
}
clone := &Config{
NDBuiltinCache: c.NDBuiltinCache,
Server: c.Server.Clone(),
Storage: c.Storage.Clone(),
Labels: maps.Clone(c.Labels),
}
if c.Services != nil {
clone.Services = make(json.RawMessage, len(c.Services))
copy(clone.Services, c.Services)
}
if c.Discovery != nil {
clone.Discovery = make(json.RawMessage, len(c.Discovery))
copy(clone.Discovery, c.Discovery)
}
if c.Bundle != nil {
clone.Bundle = make(json.RawMessage, len(c.Bundle))
copy(clone.Bundle, c.Bundle)
}
if c.Bundles != nil {
clone.Bundles = make(json.RawMessage, len(c.Bundles))
copy(clone.Bundles, c.Bundles)
}
if c.DecisionLogs != nil {
clone.DecisionLogs = make(json.RawMessage, len(c.DecisionLogs))
copy(clone.DecisionLogs, c.DecisionLogs)
}
if c.Status != nil {
clone.Status = make(json.RawMessage, len(c.Status))
copy(clone.Status, c.Status)
}
if c.Keys != nil {
clone.Keys = make(json.RawMessage, len(c.Keys))
copy(clone.Keys, c.Keys)
}
if c.Caching != nil {
clone.Caching = make(json.RawMessage, len(c.Caching))
copy(clone.Caching, c.Caching)
}
if c.DistributedTracing != nil {
clone.DistributedTracing = make(json.RawMessage, len(c.DistributedTracing))
copy(clone.DistributedTracing, c.DistributedTracing)
}
if c.DefaultDecision != nil {
s := *c.DefaultDecision
clone.DefaultDecision = &s
}
if c.DefaultAuthorizationDecision != nil {
s := *c.DefaultAuthorizationDecision
clone.DefaultAuthorizationDecision = &s
}
if c.PersistenceDirectory != nil {
s := *c.PersistenceDirectory
clone.PersistenceDirectory = &s
}
if c.Plugins != nil {
clone.Plugins = make(map[string]json.RawMessage, len(c.Plugins))
for k, v := range c.Plugins {
if v != nil {
clone.Plugins[k] = make(json.RawMessage, len(v))
copy(clone.Plugins[k], v)
}
}
}
if c.Extra != nil {
clone.Extra = make(map[string]json.RawMessage, len(c.Extra))
for k, v := range c.Extra {
if v != nil {
clone.Extra[k] = make(json.RawMessage, len(v))
copy(clone.Extra[k], v)
}
}
}
return clone
}
func (c *Config) validateAndInjectDefaults(id string) error {
if c.DefaultDecision == nil {
s := defaultDecisionPath
c.DefaultDecision = &s
}
_, err := ref.ParseDataPath(*c.DefaultDecision)
if err != nil {
return err
}
if c.DefaultAuthorizationDecision == nil {
s := defaultAuthorizationDecisionPath
c.DefaultAuthorizationDecision = &s
}
_, err = ref.ParseDataPath(*c.DefaultAuthorizationDecision)
if err != nil {
return err
}
if c.Labels == nil {
c.Labels = map[string]string{}
}
c.Labels["id"] = id
c.Labels["version"] = version.Version
return nil
}
func removeServiceCredentials(x any) error {
switch x := x.(type) {
case nil:
+24 -22
View File
@@ -277,22 +277,22 @@ func AstWithOpts(x any, opts Opts) ([]byte, error) {
}
err := w.writeModule(x)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
case *ast.Package:
_, err := w.writePackage(x, nil)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
case *ast.Import:
_, err := w.writeImports([]*ast.Import{x}, nil)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
case *ast.Rule:
_, err := w.writeRule(x, false /* isElse */, nil)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
case *ast.Head:
_, err := w.writeHead(x,
@@ -300,7 +300,7 @@ func AstWithOpts(x any, opts Opts) ([]byte, error) {
false, // isExpandedConst
nil)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
case ast.Body:
_, err := w.writeBody(x, nil)
@@ -310,27 +310,27 @@ func AstWithOpts(x any, opts Opts) ([]byte, error) {
case *ast.Expr:
_, err := w.writeExpr(x, nil)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
case *ast.With:
_, err := w.writeWith(x, nil, false)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
case *ast.Term:
_, err := w.writeTerm(x, nil)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
case ast.Value:
_, err := w.writeTerm(&ast.Term{Value: x, Location: &ast.Location{}}, nil)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
case *ast.Comment:
err := w.writeComments([]*ast.Comment{x})
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
default:
return nil, fmt.Errorf("not an ast element: %v", x)
@@ -418,7 +418,7 @@ func (w *writer) writeModule(module *ast.Module) error {
sort.Slice(comments, func(i, j int) bool {
l, err := locLess(comments[i], comments[j])
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
return l
})
@@ -426,7 +426,7 @@ func (w *writer) writeModule(module *ast.Module) error {
sort.Slice(others, func(i, j int) bool {
l, err := locLess(others[i], others[j])
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
return l
})
@@ -524,12 +524,12 @@ func (w *writer) writeRules(rules []*ast.Rule, comments []*ast.Comment) ([]*ast.
var err error
comments, err = w.insertComments(comments, rule.Location)
if err != nil && !errors.As(err, &unexpectedCommentError{}) {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
comments, err = w.writeRule(rule, false, comments)
if err != nil && !errors.As(err, &unexpectedCommentError{}) {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
if i < len(rules)-1 && w.groupableOneLiner(rule) {
@@ -874,7 +874,7 @@ func (w *writer) writeBody(body ast.Body, comments []*ast.Comment) ([]*ast.Comme
comments, err = w.writeExpr(expr, comments)
if err != nil && !errors.As(err, &unexpectedCommentError{}) {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
w.endLine()
}
@@ -1563,7 +1563,7 @@ func (w *writer) writeComprehensionBody(openChar, closeChar byte, body ast.Body,
defer w.startLine()
defer func() {
if err := w.down(); err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
}()
@@ -1627,7 +1627,7 @@ func (w *writer) writeImports(imports []*ast.Import, comments []*ast.Comment) ([
func (w *writer) writeImport(imp *ast.Import) error {
path := imp.Path.Value.(ast.Ref)
buf := []string{"import"}
w.write("import ")
if _, ok := future.WhichFutureKeyword(imp); ok {
// We don't want to wrap future.keywords imports in parens, so we create a new writer that doesn't
@@ -1638,15 +1638,17 @@ func (w *writer) writeImport(imp *ast.Import) error {
if err != nil {
return err
}
buf = append(buf, w2.buf.String())
w.write(w2.buf.String())
} else {
buf = append(buf, path.String())
_, err := w.writeRef(path, nil)
if err != nil {
return err
}
}
if len(imp.Alias) > 0 {
buf = append(buf, "as "+imp.Alias.String())
w.write(" as " + imp.Alias.String())
}
w.write(strings.Join(buf, " "))
return nil
}
@@ -1798,7 +1800,7 @@ func (w *writer) groupIterable(elements []any, last *ast.Location) ([][]any, err
slices.SortFunc(elements, func(i, j any) int {
l, err := locCmp(i, j)
if err != nil {
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, err.Error()))
w.errs = append(w.errs, ast.NewError(ast.FormatErr, &ast.Location{}, "%s", err.Error()))
}
return l
})
+21 -1
View File
@@ -9,6 +9,7 @@ import (
"fmt"
"github.com/open-policy-agent/opa/v1/config"
topdown_cache "github.com/open-policy-agent/opa/v1/topdown/cache"
)
// Hook is a hook to be called in some select places in OPA's operation.
@@ -49,6 +50,10 @@ func (hs Hooks) Each(fn func(Hook)) {
}
}
func (hs Hooks) Len() int {
return len(hs.m)
}
// ConfigHook allows inspecting or rewriting the configuration when the plugin
// manager is processing it.
// Note that this hook is not run when the plugin manager is reconfigured. This
@@ -64,10 +69,25 @@ type ConfigDiscoveryHook interface {
OnConfigDiscovery(context.Context, *config.Config) (*config.Config, error)
}
// InterQueryCacheHook allows access to the server's inter-query cache instance.
// It's useful for out-of-tree handlers that also need to evaluate something.
// Using this hook, they can share the caches with the rest of OPA.
type InterQueryCacheHook interface {
OnInterQueryCache(context.Context, topdown_cache.InterQueryCache) error
}
// InterQueryValueCacheHook allows access to the server's inter-query value cache
// instance.
type InterQueryValueCacheHook interface {
OnInterQueryValueCache(context.Context, topdown_cache.InterQueryValueCache) error
}
func (hs Hooks) Validate() error {
for h := range hs.m {
switch h.(type) {
case ConfigHook,
case InterQueryCacheHook,
InterQueryValueCacheHook,
ConfigHook,
ConfigDiscoveryHook: // OK
default:
return fmt.Errorf("unknown hook type %T", h)
+42 -15
View File
@@ -21,6 +21,7 @@ import (
"github.com/open-policy-agent/opa/v1/ast"
astJSON "github.com/open-policy-agent/opa/v1/ast/json"
"github.com/open-policy-agent/opa/v1/bundle"
"github.com/open-policy-agent/opa/v1/loader/extension"
"github.com/open-policy-agent/opa/v1/loader/filter"
"github.com/open-policy-agent/opa/v1/metrics"
"github.com/open-policy-agent/opa/v1/storage"
@@ -98,6 +99,7 @@ type FileLoader interface {
WithFilter(Filter) FileLoader
WithBundleVerificationConfig(*bundle.VerificationConfig) FileLoader
WithSkipBundleVerification(bool) FileLoader
WithBundleLazyLoadingMode(bool) FileLoader
WithProcessAnnotation(bool) FileLoader
WithCapabilities(*ast.Capabilities) FileLoader
// Deprecated: Use SetOptions in the json package instead, where a longer description
@@ -116,15 +118,16 @@ func NewFileLoader() FileLoader {
}
type fileLoader struct {
metrics metrics.Metrics
filter Filter
bvc *bundle.VerificationConfig
skipVerify bool
files map[string]bundle.FileInfo
opts ast.ParserOptions
fsys fs.FS
reader io.Reader
followSymlinks bool
metrics metrics.Metrics
filter Filter
bvc *bundle.VerificationConfig
skipVerify bool
bundleLazyLoading bool
files map[string]bundle.FileInfo
opts ast.ParserOptions
fsys fs.FS
reader io.Reader
followSymlinks bool
}
// WithFS provides an fs.FS to use for loading files. You can pass nil to
@@ -167,6 +170,12 @@ func (fl *fileLoader) WithSkipBundleVerification(skipVerify bool) FileLoader {
return fl
}
// WithBundleLazyLoadingMode enables or disables bundle lazy loading mode
func (fl *fileLoader) WithBundleLazyLoadingMode(bundleLazyLoading bool) FileLoader {
fl.bundleLazyLoading = bundleLazyLoading
return fl
}
// WithProcessAnnotation enables or disables processing of schema annotations on rules
func (fl *fileLoader) WithProcessAnnotation(processAnnotation bool) FileLoader {
fl.opts.ProcessAnnotation = processAnnotation
@@ -223,7 +232,7 @@ func (fl fileLoader) Filtered(paths []string, filter Filter) (*Result, error) {
return err
}
result, err := loadKnownTypes(path, bs, fl.metrics, fl.opts)
result, err := loadKnownTypes(path, bs, fl.metrics, fl.opts, fl.bundleLazyLoading)
if err != nil {
if !isUnrecognizedFile(err) {
return err
@@ -271,10 +280,13 @@ func (fl fileLoader) AsBundle(path string) (*bundle.Bundle, error) {
WithMetrics(fl.metrics).
WithBundleVerificationConfig(fl.bvc).
WithSkipBundleVerification(fl.skipVerify).
WithLazyLoadingMode(fl.bundleLazyLoading).
WithProcessAnnotations(fl.opts.ProcessAnnotation).
WithCapabilities(fl.opts.Capabilities).
WithFollowSymlinks(fl.followSymlinks).
WithRegoVersion(fl.opts.RegoVersion)
WithRegoVersion(fl.opts.RegoVersion).
WithLazyLoadingMode(fl.bundleLazyLoading).
WithBundleName(path)
// For bundle directories add the full path in front of module file names
// to simplify debugging.
@@ -719,8 +731,22 @@ func allRec(fsys fs.FS, path string, filter Filter, errors *Errors, loaded *Resu
}
}
func loadKnownTypes(path string, bs []byte, m metrics.Metrics, opts ast.ParserOptions) (any, error) {
switch filepath.Ext(path) {
func loadKnownTypes(path string, bs []byte, m metrics.Metrics, opts ast.ParserOptions, bundleLazyLoadingMode bool) (any, error) {
ext := filepath.Ext(path)
if handler := extension.FindExtension(ext); handler != nil {
m.Timer(metrics.RegoDataParse).Start()
var value any
err := handler(bs, &value)
m.Timer(metrics.RegoDataParse).Stop()
if err != nil {
return nil, fmt.Errorf("bundle %s: %w", path, err)
}
return value, nil
}
switch ext {
case ".json":
return loadJSON(path, bs, m)
case ".rego":
@@ -729,7 +755,7 @@ func loadKnownTypes(path string, bs []byte, m metrics.Metrics, opts ast.ParserOp
return loadYAML(path, bs, m)
default:
if strings.HasSuffix(path, ".tar.gz") {
r, err := loadBundleFile(path, bs, m, opts)
r, err := loadBundleFile(path, bs, m, opts, bundleLazyLoadingMode)
if err != nil {
err = fmt.Errorf("bundle %s: %w", path, err)
}
@@ -755,7 +781,7 @@ func loadFileForAnyType(path string, bs []byte, m metrics.Metrics, opts ast.Pars
return nil, unrecognizedFile(path)
}
func loadBundleFile(path string, bs []byte, m metrics.Metrics, opts ast.ParserOptions) (bundle.Bundle, error) {
func loadBundleFile(path string, bs []byte, m metrics.Metrics, opts ast.ParserOptions, bundleLazyLoadingMode bool) (bundle.Bundle, error) {
tl := bundle.NewTarballLoaderWithBaseURL(bytes.NewBuffer(bs), path)
br := bundle.NewCustomReader(tl).
WithRegoVersion(opts.RegoVersion).
@@ -763,6 +789,7 @@ func loadBundleFile(path string, bs []byte, m metrics.Metrics, opts ast.ParserOp
WithProcessAnnotations(opts.ProcessAnnotation).
WithMetrics(m).
WithSkipBundleVerification(true).
WithLazyLoadingMode(bundleLazyLoadingMode).
IncludeManifestInData(true)
return br.Read()
}
+11
View File
@@ -261,3 +261,14 @@ func DecisionIDFromContext(ctx context.Context) (string, bool) {
s, ok := ctx.Value(decisionCtxKey).(string)
return s, ok
}
const batchDecisionCtxKey = requestContextKey("batch_decision_id")
func WithBatchDecisionID(parent context.Context, id string) context.Context {
return context.WithValue(parent, batchDecisionCtxKey, id)
}
func BatchDecisionIDFromContext(ctx context.Context) (string, bool) {
s, ok := ctx.Value(batchDecisionCtxKey).(string)
return s, ok
}
+107 -21
View File
@@ -177,7 +177,8 @@ type StatusListener func(status map[string]*Status)
// Manager implements lifecycle management of plugins and gives plugins access
// to engine-wide components like storage.
type Manager struct {
Store storage.Store
Store storage.Store
// Config values should be accessed from the thread-safe GetConfig method.
Config *config.Config
Info *ast.Term
ID string
@@ -215,17 +216,25 @@ type Manager struct {
bootstrapConfigLabels map[string]string
hooks hooks.Hooks
enableTelemetry bool
reporter *report.Reporter
reporter report.Reporter
opaReportNotifyCh chan struct{}
stop chan chan struct{}
parserOptions ast.ParserOptions
extraRoutes map[string]ExtraRoute
extraMiddlewares []func(http.Handler) http.Handler
extraAuthorizerRoutes []func(string, []any) bool
bundleActivatorPlugin string
}
type managerContextKey string
type managerWasmResolverKey string
type (
managerContextKey string
managerWasmResolverKey string
)
const managerCompilerContextKey = managerContextKey("compiler")
const managerWasmResolverContextKey = managerWasmResolverKey("wasmResolvers")
const (
managerCompilerContextKey = managerContextKey("compiler")
managerWasmResolverContextKey = managerWasmResolverKey("wasmResolvers")
)
// SetCompilerOnContext puts the compiler into the storage context. Calling this
// function before committing updated policies to storage allows the manager to
@@ -272,7 +281,6 @@ func validateTriggerMode(mode TriggerMode) error {
// ValidateAndInjectDefaultsForTriggerMode validates the trigger mode and injects default values
func ValidateAndInjectDefaultsForTriggerMode(a, b *TriggerMode) (*TriggerMode, error) {
if a == nil && b != nil {
err := validateTriggerMode(*b)
if err != nil {
@@ -425,9 +433,15 @@ func WithTelemetryGatherers(gs map[string]report.Gatherer) func(*Manager) {
}
}
// WithBundleActivatorPlugin sets the name of the activator plugin to load bundles into the store
func WithBundleActivatorPlugin(bundleActivatorPlugin string) func(*Manager) {
return func(m *Manager) {
m.bundleActivatorPlugin = bundleActivatorPlugin
}
}
// New creates a new Manager using config.
func New(raw []byte, id string, store storage.Store, opts ...func(*Manager)) (*Manager, error) {
parsedConfig, err := config.ParseConfig(raw, id)
if err != nil {
return nil, err
@@ -442,6 +456,7 @@ func New(raw []byte, id string, store storage.Store, opts ...func(*Manager)) (*M
maxErrors: -1,
serverInitialized: make(chan struct{}),
bootstrapConfigLabels: parsedConfig.Labels,
extraRoutes: map[string]ExtraRoute{},
}
for _, f := range opts {
@@ -493,7 +508,7 @@ func New(raw []byte, id string, store storage.Store, opts ...func(*Manager)) (*M
}
if m.enableTelemetry {
reporter, err := report.New(id, report.Options{Logger: m.logger})
reporter, err := report.New(report.Options{Logger: m.logger})
if err != nil {
return nil, err
}
@@ -519,7 +534,6 @@ func New(raw []byte, id string, store storage.Store, opts ...func(*Manager)) (*M
// Init returns an error if the manager could not initialize itself. Init() should
// be called before Start(). Init() is idempotent.
func (m *Manager) Init(ctx context.Context) error {
if m.initialized {
return nil
}
@@ -536,7 +550,6 @@ func (m *Manager) Init(ctx context.Context) error {
}
err := storage.Txn(ctx, m.Store, params, func(txn storage.Transaction) error {
result, err := initload.InsertAndCompile(ctx, initload.InsertAndCompileOptions{
Store: m.Store,
Txn: txn,
@@ -545,8 +558,8 @@ func (m *Manager) Init(ctx context.Context) error {
MaxErrors: m.maxErrors,
EnablePrintStatements: m.enablePrintStatements,
ParserOptions: m.parserOptions,
BundleActivatorPlugin: m.bundleActivatorPlugin,
})
if err != nil {
return err
}
@@ -562,7 +575,6 @@ func (m *Manager) Init(ctx context.Context) error {
_, err = m.Store.Register(ctx, txn, storage.TriggerConfig{OnCommit: m.onCommit})
return err
})
if err != nil {
if m.stop != nil {
done := make(chan struct{})
@@ -581,14 +593,24 @@ func (m *Manager) Init(ctx context.Context) error {
func (m *Manager) Labels() map[string]string {
m.mtx.Lock()
defer m.mtx.Unlock()
return m.Config.Labels
return maps.Clone(m.Config.Labels)
}
// InterQueryBuiltinCacheConfig returns the configuration for the inter-query caches.
func (m *Manager) InterQueryBuiltinCacheConfig() *cache.Config {
m.mtx.Lock()
defer m.mtx.Unlock()
return m.interQueryBuiltinCacheConfig
return m.interQueryBuiltinCacheConfig.Clone()
}
// GetConfig returns a deep copy of the manager's configuration.
func (m *Manager) GetConfig() *config.Config {
m.mtx.Lock()
defer m.mtx.Unlock()
return m.Config.Clone()
}
// Register adds a plugin to the manager. When the manager is started, all of
@@ -653,6 +675,59 @@ func (m *Manager) setCompiler(compiler *ast.Compiler) {
m.compiler = compiler
}
type ExtraRoute struct {
PromName string // name is for prometheus metrics
HandlerFunc http.HandlerFunc
}
func (m *Manager) ExtraRoutes() map[string]ExtraRoute {
return m.extraRoutes
}
func (m *Manager) ExtraMiddlewares() []func(http.Handler) http.Handler {
return m.extraMiddlewares
}
func (m *Manager) ExtraAuthorizerRoutes() []func(string, []any) bool {
return m.extraAuthorizerRoutes
}
// ExtraRoute registers an extra route to be served by the HTTP
// server later. Using this instead of directly registering routes
// with GetRouter() lets the server apply its handler wrapping for
// Prometheus and OpenTelemetry.
// Caution: This cannot be used to dynamically register and un-
// register HTTP handlers. It's meant as a late-stage set up helper,
// to be called from a plugin's init methods.
func (m *Manager) ExtraRoute(path, name string, hf http.HandlerFunc) {
if _, ok := m.extraRoutes[path]; ok {
panic("extra route already registered: " + path)
}
m.extraRoutes[path] = ExtraRoute{
PromName: name,
HandlerFunc: hf,
}
}
// ExtraMiddleware registers extra middlewares (`func(http.Handler) http.Handler`)
// to be injected into the HTTP handler chain in the server later.
// Caution: This cannot be used to dynamically register and un-
// register middlewares. It's meant as a late-stage set up helper,
// to be called from a plugin's init methods.
func (m *Manager) ExtraMiddleware(mw ...func(http.Handler) http.Handler) {
m.extraMiddlewares = append(m.extraMiddlewares, mw...)
}
// ExtraAuthorizerRoute registers an extra URL path validator function for use
// in the server authorizer. These functions designate specific methods and URL
// prefixes or paths where the authorizer should allow request body parsing.
// Caution: This cannot be used to dynamically register and un-
// register path validator functions. It's meant as a late-stage
// set up helper, to be called from a plugin's init methods.
func (m *Manager) ExtraAuthorizerRoute(validatorFunc func(string, []any) bool) {
m.extraAuthorizerRoutes = append(m.extraAuthorizerRoutes, validatorFunc)
}
// GetRouter returns the managers router if set
func (m *Manager) GetRouter() *http.ServeMux {
m.mtx.Lock()
@@ -683,7 +758,6 @@ func (m *Manager) setWasmResolvers(rs []*wasm.Resolver) {
// Start starts the manager. Init() should be called once before Start().
func (m *Manager) Start(ctx context.Context) error {
if m == nil {
return nil
}
@@ -765,7 +839,9 @@ func (m *Manager) DefaultServiceOpts(config *config.Config) cfg.ServiceOptions {
}
// Reconfigure updates the configuration on the manager.
func (m *Manager) Reconfigure(config *config.Config) error {
func (m *Manager) Reconfigure(newCfg *config.Config) error {
config := newCfg.Clone()
opts := m.DefaultServiceOpts(config)
keys, err := keys.ParseKeysConfig(config.Keys)
@@ -796,6 +872,7 @@ func (m *Manager) Reconfigure(config *config.Config) error {
// don't erase persistence directory
if config.PersistenceDirectory == nil {
// update is ok since we have the lock
config.PersistenceDirectory = m.Config.PersistenceDirectory
}
@@ -846,7 +923,6 @@ func (m *Manager) UnregisterPluginStatusListener(name string) {
// listeners will be called with a copy of the new state of all
// plugins.
func (m *Manager) UpdatePluginStatus(pluginName string, status *Status) {
var toNotify map[string]StatusListener
var statuses map[string]*Status
@@ -880,7 +956,6 @@ func (m *Manager) copyPluginStatus() map[string]*Status {
}
func (m *Manager) onCommit(ctx context.Context, txn storage.Transaction, event storage.TriggerEvent) {
compiler := GetCompilerOnContext(event.Context)
// If the context does not contain the compiler fallback to loading the
@@ -908,7 +983,6 @@ func (m *Manager) onCommit(ctx context.Context, txn storage.Transaction, event s
resolvers := getWasmResolversOnContext(event.Context)
if resolvers != nil {
m.setWasmResolvers(resolvers)
} else if event.DataChanged() {
if requiresWasmResolverReload(event) {
resolvers, err := bundleUtils.LoadWasmResolversFromStore(ctx, m.Store, txn, nil)
@@ -991,7 +1065,19 @@ func (m *Manager) updateWasmResolversData(ctx context.Context, event storage.Tri
func (m *Manager) PublicKeys() map[string]*keys.Config {
m.mtx.Lock()
defer m.mtx.Unlock()
return m.keys
if m.keys == nil {
return make(map[string]*keys.Config)
}
result := make(map[string]*keys.Config, len(m.keys))
for k, v := range m.keys {
if v != nil {
copied := *v
result[k] = &copied
}
}
return result
}
// Client returns a client for communicating with a remote service.
+55 -10
View File
@@ -29,9 +29,8 @@ import (
"strings"
"time"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
"github.com/open-policy-agent/opa/internal/jwx/jws"
"github.com/open-policy-agent/opa/internal/jwx/jws/sign"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jws"
"github.com/open-policy-agent/opa/internal/providers/aws"
"github.com/open-policy-agent/opa/internal/uuid"
"github.com/open-policy-agent/opa/v1/keys"
@@ -391,11 +390,28 @@ func (ap *oauth2ClientCredentialsAuthPlugin) createAuthJWT(ctx context.Context,
case ap.AzureKeyVault != nil:
clientAssertion, err = ap.SignWithKeyVault(ctx, payload, header)
default:
clientAssertion, err = jws.SignLiteral(payload,
jwa.SignatureAlgorithm(alg),
signingKey,
header,
rand.Reader)
// Parse the algorithm string to jwa.SignatureAlgorithm
algObj, ok := jwa.LookupSignatureAlgorithm(alg)
if !ok {
return nil, fmt.Errorf("unknown signature algorithm: %s", alg)
}
// Parse headers
var headers map[string]interface{}
if err := json.Unmarshal(header, &headers); err != nil {
return nil, err
}
// Create protected headers
protectedHeaders := jws.NewHeaders()
for k, v := range headers {
if err := protectedHeaders.Set(k, v); err != nil {
return nil, err
}
}
clientAssertion, err = jws.Sign(payload,
jws.WithKey(algObj, signingKey, jws.WithProtectedHeaders(protectedHeaders)))
}
if err != nil {
return nil, err
@@ -485,8 +501,37 @@ func (ap *oauth2ClientCredentialsAuthPlugin) parseSigningKey(c Config) (err erro
return errors.New("signing_key refers to non-existent key")
}
alg := jwa.SignatureAlgorithm(ap.signingKey.Algorithm)
ap.signingKeyParsed, err = sign.GetSigningKey(ap.signingKey.PrivateKey, alg)
alg, ok := jwa.LookupSignatureAlgorithm(ap.signingKey.Algorithm)
if !ok {
return fmt.Errorf("unknown signature algorithm: %s", ap.signingKey.Algorithm)
}
// Parse the private key directly
keyData := ap.signingKey.PrivateKey
// For HMAC algorithms, return the key as bytes
if alg == jwa.HS256() || alg == jwa.HS384() || alg == jwa.HS512() {
ap.signingKeyParsed = []byte(keyData)
return nil
}
// For RSA/ECDSA algorithms, parse the PEM-encoded key
block, _ := pem.Decode([]byte(keyData))
if block == nil {
return errors.New("failed to decode PEM key")
}
switch block.Type {
case "RSA PRIVATE KEY":
ap.signingKeyParsed, err = x509.ParsePKCS1PrivateKey(block.Bytes)
case "PRIVATE KEY":
ap.signingKeyParsed, err = x509.ParsePKCS8PrivateKey(block.Bytes)
case "EC PRIVATE KEY":
ap.signingKeyParsed, err = x509.ParseECPrivateKey(block.Bytes)
default:
return fmt.Errorf("unsupported key type: %s", block.Type)
}
if err != nil {
return err
}
+113 -31
View File
@@ -126,6 +126,8 @@ type EvalContext struct {
strictBuiltinErrors bool
virtualCache topdown.VirtualCache
baseCache topdown.BaseCache
tracing tracing.Options
externalCancel topdown.Cancel // Note(philip): If non-nil, the cancellation is handled outside of this package.
}
func (e *EvalContext) RawInput() *any {
@@ -180,6 +182,18 @@ func (e *EvalContext) Transaction() storage.Transaction {
return e.txn
}
func (e *EvalContext) TracingOpts() tracing.Options {
return e.tracing
}
func (e *EvalContext) ExternalCancel() topdown.Cancel {
return e.externalCancel
}
func (e *EvalContext) QueryTracers() []topdown.QueryTracer {
return e.queryTracers
}
// EvalOption defines a function to set an option on an EvalConfig
type EvalOption func(*EvalContext)
@@ -388,6 +402,14 @@ func EvalNondeterministicBuiltins(yes bool) EvalOption {
}
}
// EvalExternalCancel sets an external topdown.Cancel for the interpreter to use
// for cancellation. This is useful for batch-evaluation of many rego queries.
func EvalExternalCancel(ec topdown.Cancel) EvalOption {
return func(e *EvalContext) {
e.externalCancel = ec
}
}
func (pq preparedQuery) Modules() map[string]*ast.Module {
mods := make(map[string]*ast.Module)
@@ -427,6 +449,7 @@ func (pq preparedQuery) newEvalContext(ctx context.Context, options []EvalOption
printHook: pq.r.printHook,
capabilities: pq.r.capabilities,
strictBuiltinErrors: pq.r.strictBuiltinErrors,
tracing: pq.r.distributedTracingOpts,
}
for _, o := range options {
@@ -625,6 +648,8 @@ type Rego struct {
bundlePaths []string
bundles map[string]*bundle.Bundle
skipBundleVerification bool
bundleActivationPlugin string
enableBundleLazyLoadingMode bool
interQueryBuiltinCache cache.InterQueryCache
interQueryBuiltinValueCache cache.InterQueryValueCache
ndBuiltinCache builtins.NDBCache
@@ -643,6 +668,8 @@ type Rego struct {
plugins []TargetPlugin
targetPrepState TargetPluginEval
regoVersion ast.RegoVersion
compilerHook func(*ast.Compiler)
evalMode *ast.CompilerEvalMode
}
func (r *Rego) RegoVersion() ast.RegoVersion {
@@ -813,7 +840,6 @@ type memo struct {
type memokey string
func memoize(decl *Function, bctx BuiltinContext, terms []*ast.Term, ifEmpty func() (*ast.Term, error)) (*ast.Term, error) {
if !decl.Memoize {
return ifEmpty()
}
@@ -1167,6 +1193,23 @@ func SkipBundleVerification(yes bool) func(r *Rego) {
}
}
// BundleActivatorPlugin sets the name of the activator plugin used to load bundles into the store.
func BundleActivatorPlugin(name string) func(r *Rego) {
return func(r *Rego) {
r.bundleActivationPlugin = name
}
}
// BundleLazyLoadingMode sets the bundle loading mode. If true, bundles will be
// read in lazy mode. In this mode, data files in the bundle will not be
// deserialized and the check to validate that the bundle data does not contain
// paths outside the bundle's roots will not be performed while reading the bundle.
func BundleLazyLoadingMode(yes bool) func(r *Rego) {
return func(r *Rego) {
r.enableBundleLazyLoadingMode = yes
}
}
// InterQueryBuiltinCache sets the inter-query cache that built-in functions can utilize
// during evaluation.
func InterQueryBuiltinCache(c cache.InterQueryCache) func(r *Rego) {
@@ -1278,9 +1321,23 @@ func SetRegoVersion(version ast.RegoVersion) func(r *Rego) {
}
}
// CompilerHook sets a hook function that will be called after the compiler is initialized.
// This is only called if the compiler has not been provided already.
func CompilerHook(hook func(*ast.Compiler)) func(r *Rego) {
return func(r *Rego) {
r.compilerHook = hook
}
}
// EvalMode lets you override the evaluation mode.
func EvalMode(mode ast.CompilerEvalMode) func(r *Rego) {
return func(r *Rego) {
r.evalMode = &mode
}
}
// New returns a new Rego object.
func New(options ...func(r *Rego)) *Rego {
r := &Rego{
parsedModules: map[string]*ast.Module{},
capture: map[*ast.Expr]ast.Var{},
@@ -1294,6 +1351,8 @@ func New(options ...func(r *Rego)) *Rego {
option(r)
}
callHook := r.compiler == nil // call hook only if we created the compiler here
if r.compiler == nil {
r.compiler = ast.NewCompiler().
WithUnsafeBuiltins(r.unsafeBuiltins).
@@ -1317,7 +1376,11 @@ func New(options ...func(r *Rego)) *Rego {
}
if r.store == nil {
r.store = inmem.NewWithOpts(inmem.OptReturnASTValuesOnRead(r.ownStoreReadAst))
if bundle.HasExtension() {
r.store = bundle.BundleExtStore()
} else {
r.store = inmem.NewWithOpts(inmem.OptReturnASTValuesOnRead(r.ownStoreReadAst))
}
r.ownStore = true
} else {
r.ownStore = false
@@ -1346,8 +1409,8 @@ func New(options ...func(r *Rego)) *Rego {
}
if r.pluginMgr != nil {
for _, name := range r.pluginMgr.Plugins() {
p := r.pluginMgr.Plugin(name)
for _, pluginName := range r.pluginMgr.Plugins() {
p := r.pluginMgr.Plugin(pluginName)
if p0, ok := p.(TargetPlugin); ok {
r.plugins = append(r.plugins, p0)
}
@@ -1358,6 +1421,14 @@ func New(options ...func(r *Rego)) *Rego {
r.compiler = r.compiler.WithEvalMode(ast.EvalModeIR)
}
if r.evalMode != nil {
r.compiler = r.compiler.WithEvalMode(*r.evalMode)
}
if r.compilerHook != nil && callHook {
r.compilerHook(r.compiler)
}
return r
}
@@ -1501,7 +1572,6 @@ func CompilePartial(yes bool) CompileOption {
// Compile returns a compiled policy query.
func (r *Rego) Compile(ctx context.Context, opts ...CompileOption) (*CompileResult, error) {
var cfg CompileContext
for _, opt := range opts {
@@ -1876,6 +1946,11 @@ func (r *Rego) parseModules(ctx context.Context, txn storage.Transaction, m metr
defer m.Timer(metrics.RegoModuleParse).Stop()
var errs Errors
popts := ast.ParserOptions{
RegoVersion: r.regoVersion,
Capabilities: r.capabilities,
}
// Parse any modules that are saved to the store, but only if
// another compile step is going to occur (ie. we have parsed modules
// that need to be compiled).
@@ -1891,7 +1966,7 @@ func (r *Rego) parseModules(ctx context.Context, txn storage.Transaction, m metr
return err
}
parsed, err := ast.ParseModuleWithOpts(id, string(bs), ast.ParserOptions{RegoVersion: r.regoVersion})
parsed, err := ast.ParseModuleWithOpts(id, string(bs), popts)
if err != nil {
errs = append(errs, err)
}
@@ -1901,7 +1976,7 @@ func (r *Rego) parseModules(ctx context.Context, txn storage.Transaction, m metr
// Parse any passed in as arguments to the Rego object
for _, module := range r.modules {
p, err := module.ParseWithOpts(ast.ParserOptions{RegoVersion: r.regoVersion})
p, err := module.ParseWithOpts(popts)
if err != nil {
switch errorWithType := err.(type) {
case ast.Errors:
@@ -1933,6 +2008,7 @@ func (r *Rego) loadFiles(ctx context.Context, txn storage.Transaction, m metrics
result, err := loader.NewFileLoader().
WithMetrics(m).
WithProcessAnnotation(true).
WithBundleLazyLoadingMode(bundle.HasExtension()).
WithRegoVersion(r.regoVersion).
WithCapabilities(r.capabilities).
Filtered(r.loadPaths.paths, r.loadPaths.filter)
@@ -1964,6 +2040,7 @@ func (r *Rego) loadBundles(_ context.Context, _ storage.Transaction, m metrics.M
bndl, err := loader.NewFileLoader().
WithMetrics(m).
WithProcessAnnotation(true).
WithBundleLazyLoadingMode(bundle.HasExtension()).
WithSkipBundleVerification(r.skipBundleVerification).
WithRegoVersion(r.regoVersion).
WithCapabilities(r.capabilities).
@@ -2022,6 +2099,8 @@ func (r *Rego) parseQuery(queryImports []*ast.Import, m metrics.Metrics) (ast.Bo
return nil, err
}
popts.SkipRules = true
popts.Capabilities = r.capabilities
return ast.ParseBodyWithOpts(r.query, popts)
}
@@ -2037,7 +2116,6 @@ func parserOptionsFromRegoVersionImport(imports []*ast.Import, popts ast.ParserO
}
func (r *Rego) compileModules(ctx context.Context, txn storage.Transaction, m metrics.Metrics) error {
// Only compile again if there are new modules.
if len(r.bundles) > 0 || len(r.parsedModules) > 0 {
@@ -2148,7 +2226,6 @@ func (r *Rego) compileQuery(query ast.Body, imports []*ast.Import, _ metrics.Met
compiled, err := qc.Compile(query)
return qc, compiled, err
}
func (r *Rego) eval(ctx context.Context, ectx *EvalContext) (ResultSet, error) {
@@ -2214,13 +2291,19 @@ func (r *Rego) eval(ctx context.Context, ectx *EvalContext) (ResultSet, error) {
}
// Cancel query if context is cancelled or deadline is reached.
c := topdown.NewCancel()
q = q.WithCancel(c)
exit := make(chan struct{})
defer close(exit)
go waitForDone(ctx, exit, func() {
c.Cancel()
})
if ectx.externalCancel == nil {
// Create a one-off goroutine to handle cancellation for this query.
c := topdown.NewCancel()
q = q.WithCancel(c)
exit := make(chan struct{})
defer close(exit)
go waitForDone(ctx, exit, func() {
c.Cancel()
})
} else {
// Query cancellation is being handled elsewhere.
q = q.WithCancel(ectx.externalCancel)
}
var rs ResultSet
err := q.Iter(ctx, func(qr topdown.QueryResult) error {
@@ -2231,7 +2314,6 @@ func (r *Rego) eval(ctx context.Context, ectx *EvalContext) (ResultSet, error) {
rs = append(rs, result)
return nil
})
if err != nil {
return nil, err
}
@@ -2304,7 +2386,6 @@ func (r *Rego) valueToQueryResult(res ast.Value, ectx *EvalContext) (ResultSet,
}
func (r *Rego) generateResult(qr topdown.QueryResult, ectx *EvalContext) (Result, error) {
rewritten := ectx.compiledQuery.compiler.RewrittenVars()
result := newResult()
@@ -2344,7 +2425,6 @@ func (r *Rego) generateResult(qr topdown.QueryResult, ectx *EvalContext) (Result
}
func (r *Rego) partialResult(ctx context.Context, pCfg *PrepareConfig) (PartialResult, error) {
err := r.prepare(ctx, partialResultQueryType, []extraStage{
{
after: "ResolveRefs",
@@ -2438,7 +2518,6 @@ func (r *Rego) partialResult(ctx context.Context, pCfg *PrepareConfig) (PartialR
}
func (r *Rego) partial(ctx context.Context, ectx *EvalContext) (*PartialQueries, error) {
var unknowns []*ast.Term
switch {
@@ -2502,13 +2581,19 @@ func (r *Rego) partial(ctx context.Context, ectx *EvalContext) (*PartialQueries,
}
// Cancel query if context is cancelled or deadline is reached.
c := topdown.NewCancel()
q = q.WithCancel(c)
exit := make(chan struct{})
defer close(exit)
go waitForDone(ctx, exit, func() {
c.Cancel()
})
if ectx.externalCancel == nil {
// Create a one-off goroutine to handle cancellation for this query.
c := topdown.NewCancel()
q = q.WithCancel(c)
exit := make(chan struct{})
defer close(exit)
go waitForDone(ctx, exit, func() {
c.Cancel()
})
} else {
// Query cancellation is being handled elsewhere.
q = q.WithCancel(ectx.externalCancel)
}
queries, support, err := q.PartialRun(ctx)
if err != nil {
@@ -2570,7 +2655,6 @@ func (r *Rego) partial(ctx context.Context, ectx *EvalContext) (*PartialQueries,
}
func (r *Rego) rewriteQueryToCaptureValue(_ ast.QueryCompiler, query ast.Body) (ast.Body, error) {
checkCapture := iteration(query) || len(query) > 1
for _, expr := range query {
@@ -2685,7 +2769,6 @@ type transactionCloser func(ctx context.Context, err error) error
// the configured Rego object. The returned function should be used to close the txn
// regardless of status.
func (r *Rego) getTxn(ctx context.Context) (storage.Transaction, transactionCloser, error) {
noopCloser := func(_ context.Context, _ error) error {
return nil // no-op default
}
@@ -2795,7 +2878,6 @@ type refResolver struct {
}
func iteration(x any) bool {
var stopped bool
vis := ast.NewGenericVisitor(func(x any) bool {
+5
View File
@@ -49,6 +49,11 @@ type MakeDirer interface {
MakeDir(context.Context, Transaction, Path) error
}
// NonEmptyer allows a store implemention to override NonEmpty())
type NonEmptyer interface {
NonEmpty(context.Context, Transaction) func([]string) (bool, error)
}
// TransactionParams describes a new transaction.
type TransactionParams struct {
+3
View File
@@ -111,6 +111,9 @@ func Txn(ctx context.Context, store Store, params TransactionParams, f func(Tran
// path is non-empty if a Read on the path returns a value or a Read
// on any of the path prefixes returns a non-object value.
func NonEmpty(ctx context.Context, store Store, txn Transaction) func([]string) (bool, error) {
if md, ok := store.(NonEmptyer); ok {
return md.NonEmpty(ctx, txn)
}
return func(path []string) (bool, error) {
if _, err := store.Read(ctx, txn, Path(path)); err == nil {
return true, nil
+77
View File
@@ -43,12 +43,40 @@ type Config struct {
InterQueryBuiltinValueCache InterQueryBuiltinValueCacheConfig `json:"inter_query_builtin_value_cache"`
}
// Clone creates a deep copy of Config.
func (c *Config) Clone() *Config {
if c == nil {
return nil
}
return &Config{
InterQueryBuiltinCache: *c.InterQueryBuiltinCache.Clone(),
InterQueryBuiltinValueCache: *c.InterQueryBuiltinValueCache.Clone(),
}
}
// NamedValueCacheConfig represents the configuration of a named cache that built-in functions can utilize.
// A default configuration to be used if not explicitly configured can be registered using RegisterDefaultInterQueryBuiltinValueCacheConfig.
type NamedValueCacheConfig struct {
MaxNumEntries *int `json:"max_num_entries,omitempty"`
}
// Clone creates a deep copy of NamedValueCacheConfig.
func (n *NamedValueCacheConfig) Clone() *NamedValueCacheConfig {
if n == nil {
return nil
}
clone := &NamedValueCacheConfig{}
if n.MaxNumEntries != nil {
maxEntries := *n.MaxNumEntries
clone.MaxNumEntries = &maxEntries
}
return clone
}
// InterQueryBuiltinValueCacheConfig represents the configuration of the inter-query value cache that built-in functions can utilize.
// MaxNumEntries - max number of cache entries
type InterQueryBuiltinValueCacheConfig struct {
@@ -56,6 +84,29 @@ type InterQueryBuiltinValueCacheConfig struct {
NamedCacheConfigs map[string]*NamedValueCacheConfig `json:"named,omitempty"`
}
// Clone creates a deep copy of InterQueryBuiltinValueCacheConfig.
func (i *InterQueryBuiltinValueCacheConfig) Clone() *InterQueryBuiltinValueCacheConfig {
if i == nil {
return nil
}
clone := &InterQueryBuiltinValueCacheConfig{}
if i.MaxNumEntries != nil {
maxEntries := *i.MaxNumEntries
clone.MaxNumEntries = &maxEntries
}
if i.NamedCacheConfigs != nil {
clone.NamedCacheConfigs = make(map[string]*NamedValueCacheConfig, len(i.NamedCacheConfigs))
for k, v := range i.NamedCacheConfigs {
clone.NamedCacheConfigs[k] = v.Clone()
}
}
return clone
}
// InterQueryBuiltinCacheConfig represents the configuration of the inter-query cache that built-in functions can utilize.
// MaxSizeBytes - max capacity of cache in bytes
// ForcedEvictionThresholdPercentage - capacity usage in percentage after which forced FIFO eviction starts
@@ -66,6 +117,32 @@ type InterQueryBuiltinCacheConfig struct {
StaleEntryEvictionPeriodSeconds *int64 `json:"stale_entry_eviction_period_seconds,omitempty"`
}
// Clone creates a deep copy of InterQueryBuiltinCacheConfig.
func (i *InterQueryBuiltinCacheConfig) Clone() *InterQueryBuiltinCacheConfig {
if i == nil {
return nil
}
clone := &InterQueryBuiltinCacheConfig{}
if i.MaxSizeBytes != nil {
maxSize := *i.MaxSizeBytes
clone.MaxSizeBytes = &maxSize
}
if i.ForcedEvictionThresholdPercentage != nil {
threshold := *i.ForcedEvictionThresholdPercentage
clone.ForcedEvictionThresholdPercentage = &threshold
}
if i.StaleEntryEvictionPeriodSeconds != nil {
period := *i.StaleEntryEvictionPeriodSeconds
clone.StaleEntryEvictionPeriodSeconds = &period
}
return clone
}
// ParseCachingConfig returns the config for the inter-query cache.
func ParseCachingConfig(raw []byte) (*Config, error) {
if raw == nil {
@@ -163,7 +163,8 @@ func (p *CopyPropagator) Apply(query ast.Body) ast.Body {
// to the current result.
// Invariant: Live vars are bound (above) and reserved vars are implicitly ground.
safe := ast.ReservedVars.Copy()
safe := ast.NewVarSetOfSize(len(p.livevars) + len(ast.ReservedVars) + 6)
safe.Update(ast.ReservedVars)
safe.Update(p.livevars)
safe.Update(ast.OutputVarsFromBody(p.compiler, result, safe))
unsafe := result.Vars(ast.SafetyCheckVisitorParams).Diff(safe)
@@ -173,9 +174,8 @@ func (p *CopyPropagator) Apply(query ast.Body) ast.Body {
providesSafety := false
outputVars := ast.OutputVarsFromExpr(p.compiler, removedEq, safe)
diff := unsafe.Diff(outputVars)
if len(diff) < len(unsafe) {
unsafe = diff
if unsafe.DiffCount(outputVars) < len(unsafe) {
unsafe = unsafe.Diff(outputVars)
providesSafety = true
}
+2 -2
View File
@@ -25,7 +25,7 @@ import (
"strings"
"time"
"github.com/open-policy-agent/opa/internal/jwx/jwk"
"github.com/lestrrat-go/jwx/v3/jwk"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/topdown/builtins"
@@ -361,7 +361,7 @@ func builtinCryptoJWKFromPrivateKey(_ BuiltinContext, operands []*ast.Term, iter
return iter(ast.InternedNullTerm)
}
key, err := jwk.New(rawKeys[0])
key, err := jwk.Import(rawKeys[0])
if err != nil {
return err
}
+1 -3
View File
@@ -561,7 +561,6 @@ func (e *eval) fmtVarTerm() string {
}
func (e *eval) evalNot(iter evalIterator) error {
expr := e.query[e.index]
if e.unknown(expr, e.bindings) {
@@ -4106,8 +4105,7 @@ func canInlineNegation(safe ast.VarSet, queries []ast.Body) bool {
SkipClosures: true,
})
vis.Walk(expr)
unsafe := vis.Vars().Diff(safe).Diff(ast.ReservedVars)
if len(unsafe) > 0 {
if vis.Vars().Diff(safe).DiffCount(ast.ReservedVars) > 0 {
return false
}
}
+119 -137
View File
@@ -7,11 +7,13 @@ package topdown
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/hmac"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
@@ -21,9 +23,9 @@ import (
"math/big"
"strings"
"github.com/open-policy-agent/opa/internal/jwx/jwa"
"github.com/open-policy-agent/opa/internal/jwx/jwk"
"github.com/open-policy-agent/opa/internal/jwx/jws"
"github.com/lestrrat-go/jwx/v3/jwk"
"github.com/lestrrat-go/jwx/v3/jws/jwsbb"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/topdown/builtins"
"github.com/open-policy-agent/opa/v1/topdown/cache"
@@ -269,6 +271,31 @@ func verifyES(publicKey any, digest []byte, signature []byte) (err error) {
return errors.New("ECDSA signature verification error")
}
// Implements EdDSA JWT signature verification
func builtinJWTVerifyEdDSA(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
result, err := builtinJWTVerify(bctx, operands[0].Value, operands[1].Value, nil, verifyEd25519)
if err == nil {
return iter(ast.InternedTerm(result))
}
return err
}
func verifyEd25519(publicKey any, digest []byte, signature []byte) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("EdDSA signature verification error: %v", r)
}
}()
publicKeyEcdsa, ok := publicKey.(ed25519.PublicKey)
if !ok {
return errors.New("incorrect public key type")
}
if ed25519.Verify(publicKeyEcdsa, digest, signature) {
return nil
}
return errors.New("ECDSA signature verification error")
}
type verificationKey struct {
alg string
kid string
@@ -309,15 +336,36 @@ func getKeysFromCertOrJWK(certificate string) ([]verificationKey, error) {
return nil, fmt.Errorf("failed to parse a JWK key (set): %w", err)
}
keys := make([]verificationKey, 0, len(jwks.Keys))
for _, k := range jwks.Keys {
key, err := k.Materialize()
if err != nil {
keys := make([]verificationKey, 0, jwks.Len())
for i := range jwks.Len() {
k, ok := jwks.Key(i)
if !ok {
continue
}
var key interface{}
if err := jwk.Export(k, &key); err != nil {
return nil, err
}
var alg string
if algInterface, ok := k.Algorithm(); ok {
alg = algInterface.String()
}
// Skip keys with unknown/unsupported algorithms
if alg != "" {
if _, ok := tokenAlgorithms[alg]; !ok {
continue
}
}
var kid string
if kidValue, ok := k.KeyID(); ok {
kid = kidValue
}
keys = append(keys, verificationKey{
alg: k.GetAlgorithm().String(),
kid: k.GetKeyID(),
alg: alg,
kid: kid,
key: key,
})
}
@@ -616,19 +664,13 @@ func (constraints *tokenConstraints) validate() error {
// verify verifies a JWT using the constraints and the algorithm from the header
func (constraints *tokenConstraints) verify(kid, alg, header, payload, signature string) error {
// Construct the payload
plaintext := []byte(header)
plaintext = append(plaintext, []byte(".")...)
plaintext = append(plaintext, payload...)
// Look up the algorithm
a, ok := tokenAlgorithms[alg]
if !ok {
return fmt.Errorf("unknown JWS algorithm: %s", alg)
}
plaintext := append(append([]byte(header), '.'), []byte(payload)...)
// If we're configured with asymmetric key(s) then only trust that
if constraints.keys != nil {
if kid != "" {
if key := getKeyByKid(kid, constraints.keys); key != nil {
err := a.verify(key.key, a.hash, plaintext, []byte(signature))
err := jwsbb.Verify(key.key, alg, plaintext, []byte(signature))
if err != nil {
return errSignatureNotVerified
}
@@ -639,7 +681,7 @@ func (constraints *tokenConstraints) verify(kid, alg, header, payload, signature
verified := false
for _, key := range constraints.keys {
if key.alg == "" {
err := a.verify(key.key, a.hash, plaintext, []byte(signature))
err := jwsbb.Verify(key.key, alg, plaintext, []byte(signature))
if err == nil {
verified = true
break
@@ -648,7 +690,7 @@ func (constraints *tokenConstraints) verify(kid, alg, header, payload, signature
if alg != key.alg {
continue
}
err := a.verify(key.key, a.hash, plaintext, []byte(signature))
err := jwsbb.Verify(key.key, alg, plaintext, []byte(signature))
if err == nil {
verified = true
break
@@ -662,7 +704,11 @@ func (constraints *tokenConstraints) verify(kid, alg, header, payload, signature
return nil
}
if constraints.secret != "" {
return a.verify([]byte(constraints.secret), a.hash, plaintext, []byte(signature))
err := jwsbb.Verify([]byte(constraints.secret), alg, plaintext, []byte(signature))
if err != nil {
return errSignatureNotVerified
}
return nil
}
// (*tokenConstraints)validate() should prevent this happening
return errors.New("unexpectedly found no keys to trust")
@@ -689,101 +735,26 @@ func (constraints *tokenConstraints) validAudience(aud ast.Value) bool {
// JWT algorithms
type (
tokenVerifyFunction func(key any, hash crypto.Hash, payload []byte, signature []byte) error
tokenVerifyAsymmetricFunction func(key any, hash crypto.Hash, digest []byte, signature []byte) error
)
// jwtAlgorithm describes a JWS 'alg' value
type tokenAlgorithm struct {
hash crypto.Hash
verify tokenVerifyFunction
}
// tokenAlgorithms is the known JWT algorithms
var tokenAlgorithms = map[string]tokenAlgorithm{
"RS256": {crypto.SHA256, verifyAsymmetric(verifyRSAPKCS)},
"RS384": {crypto.SHA384, verifyAsymmetric(verifyRSAPKCS)},
"RS512": {crypto.SHA512, verifyAsymmetric(verifyRSAPKCS)},
"PS256": {crypto.SHA256, verifyAsymmetric(verifyRSAPSS)},
"PS384": {crypto.SHA384, verifyAsymmetric(verifyRSAPSS)},
"PS512": {crypto.SHA512, verifyAsymmetric(verifyRSAPSS)},
"ES256": {crypto.SHA256, verifyAsymmetric(verifyECDSA)},
"ES384": {crypto.SHA384, verifyAsymmetric(verifyECDSA)},
"ES512": {crypto.SHA512, verifyAsymmetric(verifyECDSA)},
"HS256": {crypto.SHA256, verifyHMAC},
"HS384": {crypto.SHA384, verifyHMAC},
"HS512": {crypto.SHA512, verifyHMAC},
var tokenAlgorithms = map[string]struct{}{
"RS256": {},
"RS384": {},
"RS512": {},
"PS256": {},
"PS384": {},
"PS512": {},
"ES256": {},
"ES384": {},
"ES512": {},
"HS256": {},
"HS384": {},
"HS512": {},
"EdDSA": {},
}
// errSignatureNotVerified is returned when a signature cannot be verified.
var errSignatureNotVerified = errors.New("signature not verified")
func verifyHMAC(key any, hash crypto.Hash, payload []byte, signature []byte) error {
macKey, ok := key.([]byte)
if !ok {
return errors.New("incorrect symmetric key type")
}
mac := hmac.New(hash.New, macKey)
if _, err := mac.Write(payload); err != nil {
return err
}
if !hmac.Equal(signature, mac.Sum([]byte{})) {
return errSignatureNotVerified
}
return nil
}
func verifyAsymmetric(verify tokenVerifyAsymmetricFunction) tokenVerifyFunction {
return func(key any, hash crypto.Hash, payload []byte, signature []byte) error {
h := hash.New()
h.Write(payload)
return verify(key, hash, h.Sum([]byte{}), signature)
}
}
func verifyRSAPKCS(key any, hash crypto.Hash, digest []byte, signature []byte) error {
publicKeyRsa, ok := key.(*rsa.PublicKey)
if !ok {
return errors.New("incorrect public key type")
}
if err := rsa.VerifyPKCS1v15(publicKeyRsa, hash, digest, signature); err != nil {
return errSignatureNotVerified
}
return nil
}
func verifyRSAPSS(key any, hash crypto.Hash, digest []byte, signature []byte) error {
publicKeyRsa, ok := key.(*rsa.PublicKey)
if !ok {
return errors.New("incorrect public key type")
}
if err := rsa.VerifyPSS(publicKeyRsa, hash, digest, signature, nil); err != nil {
return errSignatureNotVerified
}
return nil
}
func verifyECDSA(key any, _ crypto.Hash, digest []byte, signature []byte) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("ECDSA signature verification error: %v", r)
}
}()
publicKeyEcdsa, ok := key.(*ecdsa.PublicKey)
if !ok {
return errors.New("incorrect public key type")
}
r, s := &big.Int{}, &big.Int{}
n := len(signature) / 2
r.SetBytes(signature[:n])
s.SetBytes(signature[n:])
if ecdsa.Verify(publicKeyEcdsa, digest, r, s) {
return nil
}
return errSignatureNotVerified
}
// JWT header parsing and parameters. See tokens_test.go for unit tests.
// tokenHeaderType represents a recognized JWT header field
@@ -882,42 +853,48 @@ func (header *tokenHeader) valid() bool {
return true
}
func commonBuiltinJWTEncodeSign(bctx BuiltinContext, inputHeaders, jwsPayload, jwkSrc string, iter func(*ast.Term) error) error {
keys, err := jwk.ParseString(jwkSrc)
func commonBuiltinJWTEncodeSign(bctx BuiltinContext, inputHeaders, jwsPayload, jwkSrc []byte, iter func(*ast.Term) error) error {
keys, err := jwk.Parse(jwkSrc)
if err != nil {
return err
}
key, err := keys.Keys[0].Materialize()
if err != nil {
return err
}
if jwk.GetKeyTypeFromKey(key) != keys.Keys[0].GetKeyType() {
return errors.New("JWK derived key type and keyType parameter do not match")
}
standardHeaders := &jws.StandardHeaders{}
jwsHeaders := []byte(inputHeaders)
err = json.Unmarshal(jwsHeaders, standardHeaders)
if err != nil {
return err
}
alg := standardHeaders.GetAlgorithm()
if alg == jwa.Unsupported {
return errors.New("unknown signature algorithm")
if keys.Len() == 0 {
return errors.New("no keys found in JWK set")
}
if (standardHeaders.Type == "" || standardHeaders.Type == headerJwt) && !json.Valid([]byte(jwsPayload)) {
key, ok := keys.Key(0)
if !ok {
return errors.New("failed to get first key from JWK set")
}
// Parse headers to get algorithm.
headers := jwsbb.HeaderParse(inputHeaders)
algStr, err := jwsbb.HeaderGetString(headers, "alg")
if err != nil {
return fmt.Errorf("missing or invalid 'alg' header: %w", err)
}
// Make sure the algorithm is supported.
_, ok = tokenAlgorithms[algStr]
if !ok {
return fmt.Errorf("unknown JWS algorithm: %s", algStr)
}
typ, err := jwsbb.HeaderGetString(headers, "typ")
if (err != nil || typ == headerJwt) && !json.Valid(jwsPayload) {
return errors.New("type is JWT but payload is not JSON")
}
// process payload and sign
var jwsCompact []byte
jwsCompact, err = jws.SignLiteral([]byte(jwsPayload), alg, key, jwsHeaders, bctx.Seed)
payload := jwsbb.SignBuffer(nil, inputHeaders, jwsPayload, base64.RawURLEncoding, true)
signature, err := jwsbb.Sign(key, algStr, payload, bctx.Seed)
if err != nil {
return err
}
return iter(ast.StringTerm(string(jwsCompact)))
jwsCompact := string(payload) + "." + base64.RawURLEncoding.EncodeToString(signature)
return iter(ast.StringTerm(jwsCompact))
}
func builtinJWTEncodeSign(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
@@ -953,9 +930,9 @@ func builtinJWTEncodeSign(bctx BuiltinContext, operands []*ast.Term, iter func(*
return commonBuiltinJWTEncodeSign(
bctx,
string(inputHeadersBs),
string(payloadBs),
string(signatureBs),
inputHeadersBs,
payloadBs,
signatureBs,
iter,
)
}
@@ -973,7 +950,7 @@ func builtinJWTEncodeSignRaw(bctx BuiltinContext, operands []*ast.Term, iter fun
if err != nil {
return err
}
return commonBuiltinJWTEncodeSign(bctx, string(inputHeaders), string(jwsPayload), string(jwkSrc), iter)
return commonBuiltinJWTEncodeSign(bctx, []byte(inputHeaders), []byte(jwsPayload), []byte(jwkSrc), iter)
}
// Implements full JWT decoding, validation and verification.
@@ -1248,6 +1225,10 @@ func extractJSONObject(s string) (ast.Object, error) {
// getInputSha returns the SHA checksum of the input
func getInputSHA(input []byte, h func() hash.Hash) []byte {
if h == nil {
return input
}
hasher := h()
hasher.Write(input)
return hasher.Sum(nil)
@@ -1320,6 +1301,7 @@ func init() {
RegisterBuiltinFunc(ast.JWTVerifyES256.Name, builtinJWTVerifyES256)
RegisterBuiltinFunc(ast.JWTVerifyES384.Name, builtinJWTVerifyES384)
RegisterBuiltinFunc(ast.JWTVerifyES512.Name, builtinJWTVerifyES512)
RegisterBuiltinFunc(ast.JWTVerifyEdDSA.Name, builtinJWTVerifyEdDSA)
RegisterBuiltinFunc(ast.JWTVerifyHS256.Name, builtinJWTVerifyHS256)
RegisterBuiltinFunc(ast.JWTVerifyHS384.Name, builtinJWTVerifyHS384)
RegisterBuiltinFunc(ast.JWTVerifyHS512.Name, builtinJWTVerifyHS512)
+16 -21
View File
@@ -17,6 +17,22 @@ import (
"github.com/open-policy-agent/opa/v1/util"
)
var (
// Nl represents an instance of the null type.
Nl Type = NewNull()
// B represents an instance of the boolean type.
B Type = NewBoolean()
// S represents an instance of the string type.
S Type = NewString()
// N represents an instance of the number type.
N Type = NewNumber()
// A represents the superset of all types.
A Type = NewAny()
// Boxed set types.
SetOfAny, SetOfStr, SetOfNum Type = NewSet(A), NewSet(S), NewSet(N)
)
// Sprint returns the string representation of the type.
func Sprint(x Type) string {
if x == nil {
@@ -50,8 +66,6 @@ func NewNull() Null {
return Null{}
}
var Nl Type = NewNull()
// NamedType represents a type alias with an arbitrary name and description.
// This is useful for generating documentation for built-in functions.
type NamedType struct {
@@ -116,9 +130,6 @@ func (Null) String() string {
// Boolean represents the boolean type.
type Boolean struct{}
// B represents an instance of the boolean type.
var B Type = NewBoolean()
// NewBoolean returns a new Boolean type.
func NewBoolean() Boolean {
return Boolean{}
@@ -139,9 +150,6 @@ func (t Boolean) String() string {
// String represents the string type.
type String struct{}
// S represents an instance of the string type.
var S Type = NewString()
// NewString returns a new String type.
func NewString() String {
return String{}
@@ -161,9 +169,6 @@ func (String) String() string {
// Number represents the number type.
type Number struct{}
// N represents an instance of the number type.
var N Type = NewNumber()
// NewNumber returns a new Number type.
func NewNumber() Number {
return Number{}
@@ -256,13 +261,6 @@ type Set struct {
of Type
}
// Boxed set types.
var (
SetOfAny Type = NewSet(A)
SetOfStr Type = NewSet(S)
SetOfNum Type = NewSet(N)
)
// NewSet returns a new Set type.
func NewSet(of Type) *Set {
return &Set{
@@ -513,9 +511,6 @@ func mergeObjects(a, b *Object) *Object {
// Any represents a dynamic type.
type Any []Type
// A represents the superset of all types.
var A Type = NewAny()
// NewAny returns a new Any type.
func NewAny(of ...Type) Any {
sl := make(Any, len(of))
+2
View File
@@ -17,6 +17,8 @@ func DefaultBackoff(base, maxNS float64, retries int) time.Duration {
// Backoff returns a delay with an exponential backoff based on the number of
// retries. Same algorithm used in gRPC.
// Note that if maxNS is smaller than base, the backoff will still be capped at
// maxNS.
func Backoff(base, maxNS, jitter, factor float64, retries int) time.Duration {
if retries == 0 {
return 0
+11
View File
@@ -62,3 +62,14 @@ func NumDigitsUint(n uint64) int {
return int(math.Log10(float64(n))) + 1
}
// KeysCount returns the number of keys in m that satisfy predicate p.
func KeysCount[K comparable, V any](m map[K]V, p func(K) bool) int {
count := 0
for k := range m {
if p(k) {
count++
}
}
return count
}
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"runtime/debug"
)
var Version = "1.6.0"
var Version = "1.8.0"
// GoVersion is the version of Go this was built with
var GoVersion = runtime.Version()