// Copyright 2020 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. package ast import ( "bytes" _ "embed" "encoding/json" "fmt" "io" "os" "sort" "strings" caps "github.com/open-policy-agent/opa/capabilities" "github.com/open-policy-agent/opa/internal/semver" "github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities" "github.com/open-policy-agent/opa/util" ) // VersonIndex contains an index from built-in function name, language feature, // and future rego keyword to version number. During the build, this is used to // create an index of the minimum version required for the built-in/feature/kw. type VersionIndex struct { Builtins map[string]semver.Version `json:"builtins"` Features map[string]semver.Version `json:"features"` Keywords map[string]semver.Version `json:"keywords"` } // NOTE(tsandall): this file is generated by internal/cmd/genversionindex/main.go // and run as part of go:generate. We generate the version index as part of the // build process because it's relatively expensive to build (it takes ~500ms on // my machine) and never changes. // //go:embed version_index.json var versionIndexBs []byte var minVersionIndex = func() VersionIndex { var vi VersionIndex err := json.Unmarshal(versionIndexBs, &vi) if 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 // OPA can work with policies that we're compiling -- if they don't know ref // heads, they wouldn't be able to parse them. const FeatureRefHeadStringPrefixes = "rule_head_ref_string_prefixes" const FeatureRefHeads = "rule_head_refs" const FeatureRegoV1Import = "rego_v1_import" // Capabilities defines a structure containing data that describes the capabilities // or features supported by a particular version of OPA. type Capabilities struct { Builtins []*Builtin `json:"builtins,omitempty"` FutureKeywords []string `json:"future_keywords,omitempty"` WasmABIVersions []WasmABIVersion `json:"wasm_abi_versions,omitempty"` // Features is a bit of a mixed bag for checking that an older version of OPA // is able to do what needs to be done. // TODO(sr): find better words ^^ Features []string `json:"features,omitempty"` // allow_net is an array of hostnames or IP addresses, that an OPA instance is // allowed to connect to. // If omitted, ANY host can be connected to. If empty, NO host can be connected to. // As of now, this only controls fetching remote refs for using JSON Schemas in // the type checker. // TODO(sr): support ports to further restrict connection peers // TODO(sr): support restricting `http.send` using the same mechanism (see https://github.com/open-policy-agent/opa/issues/3665) AllowNet []string `json:"allow_net,omitempty"` } // WasmABIVersion captures the Wasm ABI version. Its `Minor` version is indicating // backwards-compatible changes. type WasmABIVersion struct { Version int `json:"version"` Minor int `json:"minor_version"` } // CapabilitiesForThisVersion returns the capabilities of this version of OPA. func CapabilitiesForThisVersion() *Capabilities { f := &Capabilities{} for _, vers := range capabilities.ABIVersions() { f.WasmABIVersions = append(f.WasmABIVersions, WasmABIVersion{Version: vers[0], Minor: vers[1]}) } f.Builtins = make([]*Builtin, len(Builtins)) copy(f.Builtins, Builtins) sort.Slice(f.Builtins, func(i, j int) bool { return f.Builtins[i].Name < f.Builtins[j].Name }) for kw := range futureKeywords { f.FutureKeywords = append(f.FutureKeywords, kw) } sort.Strings(f.FutureKeywords) f.Features = []string{ FeatureRefHeadStringPrefixes, FeatureRefHeads, FeatureRegoV1Import, } return f } // LoadCapabilitiesJSON loads a JSON serialized capabilities structure from the reader r. func LoadCapabilitiesJSON(r io.Reader) (*Capabilities, error) { d := util.NewJSONDecoder(r) var c Capabilities return &c, d.Decode(&c) } // LoadCapabilitiesVersion loads a JSON serialized capabilities structure from the specific version. func LoadCapabilitiesVersion(version string) (*Capabilities, error) { cvs, err := LoadCapabilitiesVersions() if err != nil { return nil, err } for _, cv := range cvs { if cv == version { cont, err := caps.FS.ReadFile(cv + ".json") if err != nil { return nil, err } return LoadCapabilitiesJSON(bytes.NewReader(cont)) } } return nil, fmt.Errorf("no capabilities version found %v", version) } // LoadCapabilitiesFile loads a JSON serialized capabilities structure from a file. func LoadCapabilitiesFile(file string) (*Capabilities, error) { fd, err := os.Open(file) if err != nil { return nil, err } defer fd.Close() return LoadCapabilitiesJSON(fd) } // LoadCapabilitiesVersions loads all capabilities versions func LoadCapabilitiesVersions() ([]string, error) { ents, err := caps.FS.ReadDir(".") if err != nil { return nil, err } capabilitiesVersions := make([]string, 0, len(ents)) for _, ent := range ents { capabilitiesVersions = append(capabilitiesVersions, strings.Replace(ent.Name(), ".json", "", 1)) } return capabilitiesVersions, nil } // 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 if err := maxVersion.Set("0.17.0"); err != nil { panic("unreachable") } for _, bi := range c.Builtins { v, ok := minVersionIndex.Builtins[bi.Name] if !ok { return "", false } if v.Compare(maxVersion) > 0 { maxVersion = v } } for _, kw := range c.FutureKeywords { v, ok := minVersionIndex.Keywords[kw] if !ok { return "", false } if v.Compare(maxVersion) > 0 { maxVersion = v } } for _, feat := range c.Features { v, ok := minVersionIndex.Features[feat] if !ok { return "", false } if v.Compare(maxVersion) > 0 { maxVersion = v } } return maxVersion.String(), true } func (c *Capabilities) ContainsFeature(feature string) bool { for _, f := range c.Features { if f == feature { return true } } return false } // addBuiltinSorted inserts a built-in into c in sorted order. An existing built-in with the same name // will be overwritten. func (c *Capabilities) addBuiltinSorted(bi *Builtin) { i := sort.Search(len(c.Builtins), func(x int) bool { return c.Builtins[x].Name >= bi.Name }) if i < len(c.Builtins) && bi.Name == c.Builtins[i].Name { c.Builtins[i] = bi return } c.Builtins = append(c.Builtins, nil) copy(c.Builtins[i+1:], c.Builtins[i:]) c.Builtins[i] = bi }