Commit Graph

46 Commits

Author SHA1 Message Date
Sandokan D. Arantes 1731907295 topdown: Add built-in HMAC compare function
Add crypto.hmac.equal built-in function to safelly comparing hashes generated by MD5, SHA-1, SHA-256 and SHA-512 hashing algorithms.

The built-in function is a wrapped for the function Equal of package crypto/hmac.

It's useful when you need to write a policy that requires to check hash signature for request body.

Signed-off-by: Sandokan D. Arantes <sandokandias@gmail.com>
2023-04-21 10:24:07 -07:00
Yuri Kulagin 334f27e0d1 topdown: JSON Schema validation and verification (#5486)
Adding built-in functions for JSON schema validation

* json.verify_schema
* json.match_schema

Signed-off-by: Yuri Kulagin <jkulvichi@gmail.com>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Co-authored-by: Johan Fylling <johan.dev@fylling.se>
2023-03-02 11:54:48 +01:00
Aviral Jain 34f6939a97 time.format: new builtin to get string timestamp for ns (#5432)
Adds a builtin time.format to format time given in ns to a string timestamp for
the given timezone or UTC as default. The builtin takes in 3 types of arguments:

1. An integer value representing the time in nanoseconds since epoch
2. An array with the first value as integer representing the time in ns and second argument as string value representing the timezone. In the first case, when only an integer value is provided, UTC timezone is considered
    The function returns a string type value of the timestamp in the RFC3339Nano format. E.g. 2022-11-23T18:20:14Z
3. An array with the first value the integer ns, the second a string timezone, and third a string for the format, to use one different from RFC3339Nano.

Signed-off-by: burnerlee <avi.aviral140@gmail.com>
2022-12-19 10:32:41 +01:00
Philip Conrad ed76301eb6 builtins: Add AWS Sig v4 signing builtin. (#5376)
This commit adds initial support for AWS's SigV4 request signing system,
which will allow OPA's existing `http.send` builtin to be used to more
conveniently query cloud resources. It automates away most of the pain
around signing the request headers and body, and is designed to compose
with `http.send` directly.

Internally, this also refactors AWS SigV4 request signing, so that the
signing logic is shared between the builtin and the REST plugin for AWS.

Fixes: #3749

Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
2022-11-18 15:11:47 -05:00
Kevin Swiber b6184bd258 builtins: add object.keys (#5392)
The `object.keys` function will return a set of all top-level keys on
a given object.  Since object keys in Rego don't have the same
restrictions as names in JSON name-value pairs, we also ensure
support for non-string key types.

Fixes #5363.

Signed-off-by: Kevin Swiber <kswiber@gmail.com>
2022-11-17 20:55:35 +01:00
Philip Conrad 6ec07cabfb ast+topdown: Add graphql.schema_is_valid builtin. (#5296)
This commit adds a new GraphQL builtin for validating GraphQL schemas,
applying stronger validation rules than what the current GraphQL parsing
builtins apply by default.

Fixes: #5125

Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
2022-10-27 14:59:18 +02:00
Ricardo Maraschini da8afed08d builtins: add net.cidr_is_valid for validating CIDR strings (#5306)
This commit adds the `net.cidr_is_valid` builtin, which makes
validating network CIDR strings much easier in policies.

Example policy:

    allow {
        net.cidr_is_valid("192.168.0.0/24")
    }

This builtin works for both IPv4 and IPv6 CIDR strings.

Signed-off-by: Ricardo Maraschini <ricardo.maraschini@gmail.com>
2022-10-26 12:44:31 -04:00
Stephan Renatus 2f264b12f1 ast: add 'features' to capabilities, use it to check for ref heads (#5208)
Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-10-14 10:38:04 +02:00
Boran Seref 1d2173ba9d feat: add regex.replace(s, p, v) to builtin (#5179)
* feat: add regex.replace(s, p, v) to builtin

Signed-off-by: boranx <boran.seref@gmail.com>
2022-09-27 19:27:24 +02:00
Philip Conrad ab385e9c17 topdown/builtins: Add object support for GraphQL builtins. (#4752)
This commit adds support for AST objects to be usable in place of
strings for several of the GraphQL built-in functions, to improve
the composability of the GraphQL set of built-ins, and to dramatically
reduce the amount of redundant parsing when writing GraphQL policies.

Fixes: #4742

Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
2022-09-26 11:47:40 +02:00
Philip Conrad 01fc9ec013 eval+rego: Support caching output of non-deterministic builtins. (#4926)
This commit includes evaluator support for an opt-in, non-deterministic
builtins caching system, designed to help with future replay of decision
logs.

The cache allows early-exit in the evaluator if the builtin is
non-deterministic, and has already cached a result. Since the cache can
be pre-populated by `rego` module users, this should make offline policy
testing and future work around decision replay more straightforward.

Fixes: #1514

Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
2022-09-01 11:35:09 -04:00
Jacob Martin e7130a888d Add any_prefix_match and any_suffix_match functions for bulk prefix and suffix matching. (#4997)
Signed-off-by: Jakub Martin <kubam@spacelift.io>
2022-08-18 21:33:59 +02:00
Phạm Hữu Vinh da4a10044b topdown: support glob.match without delimiters (#4933)
Using null for delimiters disables delimiters in glob matching. Preferable over regex on some cases for performance reasons.

Fixes #4923.

Signed-off-by: vinhph0906 <vinhph0906@gmail.com>
Co-authored-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-07-25 16:51:28 +02:00
Stephan Renatus 855c6bd44a ast/builtins: fix 'type_name' type and docs (#4920)
Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-07-21 09:41:17 +02:00
Stephan Renatus 04a3523b22 ast+format: introduce new keywords for rule heads: if and contains
`contains` provides an alternative way to declare partial sets:

    p contains x {
      x := { "foo": "bar"
    }

which is the same as

    p[x] {
      x := { "foo": "bar"
    }

The keyword is enabled by importing `future.keywords.contains`, and
when it _is enabled_, the format will be used for all partial sets in
that file for pretty-printing.

`if` is a new keyword allowing for more readable rule definitions:

The syntax is

    NAME [if] { EXPR [EXPR...] }

and the is a shorthand allows dropping the braces around the expression
if there is only one:

    NAME if EXPR

For example, this allows expressions like

    allow if not deny
    f(xs) if every x in xs { x != "foo" }

The one exception here are partial sets: they cannot use `if` UNLESS
they use `contains`:

    p[x] { x := "foo" }            # valid
    p contains x { x := "bar" }    # valid
    p contains x if { x := "bar" } # valid
    p[x] if { x := "foo" }         # invalid

This is because we want to interpret that differently (as an object
rule defining `p.foo = true`) in the near future.

The formatter works in the same way: if `future.keywords.if` is imported, it
will be used where it can be used.

We don't want to be too eager when it comes to introducing syntactic sugar.

So this will be rewritten, because head and body expression are on the same
line:

    p := 5 if { time.day_of_week() == "Monday" }

    # => p := 5 if time.day_of_week() == "Monday"

but this won't:

    p := 5 if {
       time.day_of_week() == "Monday"
    }

The rationale here is that if the policy author decided that they want this on
an extra line, we won't mess with it.

This also sidesteps the need to check if both the head and the single body
expression have a comment.

This change includes various docs updates. Notable exceptions are the GK docs,
since it will take a while for these keywords to be come available there; and
the frontpage: merging a PR would update the frontpage immediately, and we
don't want to show something there that isn't available in the latest release.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-06-22 10:36:06 +02:00
Charles Daniels f2fd8f5e91 built-ins: add object.subset() builtin
This implements the new object.subset() builtin.

Based on my benchmarking, this offers a 2.77x speedup compared to
implementing the same thing in pure Rego, and is also easier to read.

Fixes #4358

Signed-off-by: Charles Daniels <charles@styra.com>
2022-06-09 13:57:54 -07:00
Philip Conrad 1fde1ad235 built-ins: Add new GraphQL builtins.
This commit vendors in github.com/vektah/gqlparser, and provides the
implementation for the following graphql built-ins:

 - graphql.parse
 - graphql.parse_and_verify
 - graphql.parse_query
 - graphql.parse_schema
 - graphql.is_valid

The test suite is comprised of the classic "Star Wars" examples from the
official GraphQL docs, along with a host of syntax examples ported over
from the underlying GraphQL parser's test suite.

AST objects returned by the parse_x APIs are currently pruned for brevity.

Fixes #4283

Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
2022-05-27 10:26:42 -07:00
Philip Conrad 53cb5f40f9 topdown: Add units.parse builtin (#4676)
This function works on all base decimal and binary SI units of the set:

    m, K/Ki, M/Mi, G/Gi, T/Ti, P/Pi, and E/Ei

Note: Unlike `units.parse_bytes`, this function is case sensitive.

Fixes open-policy-agent#1802.

Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
2022-05-11 16:54:51 +02:00
Johan Fylling c6226621c4 ast: Adding rego.metadata.* built-in functions (#4537)
New functions:
* rego.metadata.chain(): returns the chain of metadata, starting from the active rule, going outward
* rego.metadata.rule(): returns the metadata for the active rule

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2022-04-13 11:46:11 +02:00
Stephan Renatus a96e1779f3 ast+format: unveil future keywords 'every', forbit negation, copy *Every
Importing `future.keywords.every` will ALSO import `future.keywords.in`,
since the latter is required for the former.

This includes the formatting of the expression itself, and adding
the "future.keywors.every" import if necessary:

This would happen when pretty-printing an AST that was parsed
with ast.ParserOptions enabling the required future keyword:
The import would not be present in the *ast.Module, but it would
be required to parse the pretty-printed result.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2022-02-11 11:22:19 +01:00
Anders Eknert caffcec07b builtins: add object.union_n
Fixes: #4012

Signed-off-by: Anders Eknert <anders@eknert.com>
2022-01-23 20:24:57 +01:00
Justin Lindh 258501134d built-ins: add graph.reachable_paths (#4205)
This new built-in functionality allows callers to find all reachable
paths in a graph based on an array or set of root nodes. See the
updates to policy-reference.md for more details and usage information.

Signed-off-by: Justin Lindh <justin.lindh@webfilings.com>
2022-01-17 09:31:31 +01:00
Shuhei Kitagawa 52ddfd9149 topdown: Support indexof_n built-in function (#4172)
Fixes https://github.com/open-policy-agent/opa/issues/4155

Signed-off-by: shuheiktgw <s-kitagawa@mercari.com>
2022-01-06 09:58:27 +01:00
Kristian Svalland 6f81c4a620 Add array.reverse(array) and strings.reverse(string) built-in functions. (#4161)
The function `array.reverse` takes an array as an argument, and returns an array with a reversed order of elements.
The function `strings.reverse` takes a string as an argument, and returns a string with a reversed order of unicode code points.
WASM support is included for both built-ins.

Fixes #3736

Signed-off-by: Kristian Svalland <kristian.svalland@gmail.com>
2021-12-27 12:47:39 +01:00
Johan Fylling 76547e55d1 topdown: Add built-in HMAC functions (#4100)
Add crypto.hmac.* built-in functions for the MD5, SHA-1, SHA-256 and SHA-512 hashing algorithms.

Add documentation for how to contribute new built-in functions.

Fixes: #1740
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
2021-12-09 13:48:17 +01:00
Stephan Renatus 8a1aab376f ast+topdown: add net.lookup_ip_addr built-in function (#3995)
Since the golang stdlib function doesn't do any caching, we add the result
to the BuiltinContext.Cache so it's cached, and consistent, within a single
policy evaluation.

There is no decision made here about using netgo or netcgo: we're following
suit wrt how golang expects you to do it: From my understanding, using the
OS means for DNS resolution is the preferred way: it gives you per-host
caching, and it allows the user to affect how DNS resolution works in many
ways.

This means the same logic that applies to all other places where we resolve
domain names into addresses (notably `http.send`) applies to this built-in,
too.

Also:

* workflow/pull_request: don't fail-fast for matrix jobs

Even if one platform fails it would be interesting to see what happens
on the others.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2021-11-16 07:39:14 +01:00
Torin Sandall 04ead1d638 ast: Add future keywords to capabilities
This commit updates the capabilities structure to include the set of
supported future keywords and enhances the parser to accept the
capabilities structure so that callers can restrict what keywords can
be opted into in the first place. This ensures that callers can verify
that policies will parse for a particular version of OPA.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
2021-10-20 14:16:52 -07:00
Stephan Renatus f8286be64e ast, topdown: add 'in' operator via internal builtins and future imports
There are four variants to this: with or without 'some', and with
only one or two lhs arguments:

 a) x in xs
 b) k, v in xs
 c) some x in xs
 d) some k, v in xs

(a) and (b) are handled in the parser, and end up in the AST as
calls to `internal.member_2` and `internal.member_3`:

 a) internal.member_2(x, xs)
 b) internal.member_4(k, v, xs)

The backing builtin functions iterate over their last arguments,
trying to find a match. If they do, they will return `true`.
In all other cases -- no match, or a type in the last argument that
can't be iterated over (not an array, object or set), it will
return `false`.

As such, they can be used with `not` without any restrictions.

(c) and (d) are rewritten in the compiler, where x', v', and k' are
fresh local vars:

 c) x' = xs[_]
 d) v' = xs[k']

Since `in` is a new keyword, it's enabled gradually: for now, a new
mechanism of future keyword imports is added. There are new option
arguments in the parser methods, and there's a hook in the parser
code updating its set of enabled future keywords whenever it
encounters an import statement like

    import future.keywords.in # enables only "in"
    import future.keywords    # enables all future keywords

Functionally, these are identical right now: there is only one
future keyword, "in".

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2021-10-14 19:21:52 +02:00
Torin Sandall 8c77e5cb7c ast, topdown: Add print built-in function
The print built-in function has special-case handling in two ways:

1. Print arguments are wrapped in comprehensions at compile-time to
ensure that undefined values do not short-circuit evaluation. The
evaluator is aware of this rewriting and extracts the actual values
during evaluation.

2. Print arguments cannot contain unsafe/undeclared variables, i.e.,
vars appearing in refs inside of print args _must_ be assigned in
a previous expression in the body. This ensures that print statements
do not affect the semantics of the rule.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
2021-10-14 09:31:16 -07:00
Torin Sandall f4d9c2225d types: Sort any elements during construction (#3805)
* types: Sort any elements during construction

This changes updates the implementation of the any type to sort
elements during construction. This way the Compare() function does not
have to sort elements before recursing (which can result in data races
if global type instances from the built-in function declarations or
elsewhere are compared.)

Fixes #3793

* capabilities.json: changed ordering
* internal/presentation: fix json error output

Co-authored-by: Torin Sandall <torinsandall@gmail.com>
Co-authored-by: Stephan Renatus <stephan.renatus@gmail.com>
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
2021-09-20 11:46:02 +02:00
Cris He 22d505fd9e built-ins: decode a RSA private key into JWK format (#3783)
This will help users for JWT signing using RSA key, because currently
OPA only accepts RSA key in the JWK format.

Fixes: #3765

Signed-off-by: cris-he <cruztiempo@hotmail.com>
2021-09-17 07:03:16 +02:00
Stephan Renatus c0c3cd18a6 wasm: introduce one-off eval function, use it instead (#3627)
* wasm/sdk: check version, call old eval path for ABI 1.1

  Fixes #3146.

* docs/wasm: document addition as ABI 1.2

* wasm-sdk: overwrite previous inputs, don't accumulate them

  There is a little room for optimization here, should the input
  ever grow so large that it eats up too much precious heap space,
  we could look into changing this so that the memory used for it
  can be reclaimed.

* internal/compiler/wasm: commit generated wasm

  I've noticed that since the CI build running on macos-latest doesn't
  have docker installed, it cannot update these files itself at build
  time. We thus end up with macos binaries that have the wasm binary
  data from the main branch, not the PR.

  This can be observed from the test failure:

      Run make ci-binary-smoke-test-wasm BINARY=opa_darwin_amd64
      chmod +x "_release/0.31.0-dev/opa_darwin_amd64"
      "_release/0.31.0-dev/opa_darwin_amd64" eval -t "wasm" 'time.now_ns()'
      make: *** [ci-binary-smoke-test-wasm] Error 2
      {
        "errors": [
          {
            "message": "caller not found: opa_eval (opa_eval)"
          }
        ]
      }
      Error: Process completed with exit code 2.

  Since I had previously commit the CSV data that drives the dead
  code elimination process, that optimization had failed to find a
  function it expected to have.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2021-07-15 09:41:40 +02:00
Justin Thomas abb8d0c310 feat rand.intn: generate random number 0-n
fix: error for < 0

fix: deterministic random

fix: cache key str op, formatting

fix: go lint empty err
Signed-off-by: Justin Thomas <jthomas@vdms.com>
2021-07-12 14:50:15 -07:00
James Alseth c354e3c4f2 Add crypto.x509.parse_and_verify_certificates (#3602)
This allows users to supply a certificate chain and verify that the leaf
certificate has a path back to the supplied root.

Fixes #3601.

Signed-off-by: James Alseth <james@jalseth.me>
2021-07-05 21:59:59 +02:00
André Håland 458d87489b built-ins: time.diff function (#3358)
This built-in function makes it possible to get the absolute
difference between to unix timestamps (nanoseconds since epoch)
on the format [year, month, day, hour, minute, second].

Fixes: #3348

Signed-off-by: Andre Håland <andre.haland@gmail.com>
2021-04-13 14:30:08 +02:00
Torin Sandall 737f1a9eb1 wasm: Fix memory leak by using caller to obtain exports
There was a memory leak in the VM that was caused by circular
dependencies on the objects shared between Go and Rust. The circular
dependencies existed because the functions used for the built-ins and
opa_abort were closing over wasmtime.Store-related objects.

By using the Caller to obtain the exports, we can break the circular
dependency and avoid the memory leak.

This commit relies on the fix for
https://github.com/bytecodealliance/wasmtime-go/pull/59 which is in
the development version of wasmtime{-go} that is vendored in a
previous commit.

This change bumps the ABI minor version (1.0 => 1.1)

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
2021-02-18 15:25:21 -05:00
Stephan Renatus f817ea5a32 capabilities: add minor version
Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2021-02-17 15:35:57 -05:00
Stephan Renatus 30a06544f2 wasm: introduce abi_versions to capabilities (#3142)
* wasm: emit ABI version as global

This takes inspiration from the proxy-spec (Envoy's Wasm support).
There, it's recorded in an exported function's name. However, it's
been included like that in the spec because it's the least common
denominator among the different languages (potentially) used to
implement proxy-spec. We've got a pretty good grip on our generated
Wasm code, so we do what's noted in proxy-spec as "ideally, we'd do
xyz instead".

However, our ABI version is a simple integer, no semver.

Ref: https://github.com/proxy-wasm/spec/tree/master/abi-versions/vNEXT#proxy_abi_version_x_y_z

* ast.CapabilitiesForThisVersion: include WasmABIVersions

Extending the ast.Capabilities like this is somewhat unsatisfying -- the Wasm ABI has little to do with the ast package. However, moving Capabilities outside of ast in a way that's not introducing import cycles and is backwards-compatible proved to be quite an effort; so let's go with "simple" here.

* capatibilities.json: ensure it is generated with ABI versions

The build tag `generate` is what `go generate` would set, too. We're losing
that in the main.go -> gen-run-go.sh indirection, so we've got to set it
ourselves.

* ci: fix npm-opa-wasm e2e test

The CI build uses a version of OPA built in a previous step -- with the Wasm SDK _disabled_.
To still build Wasm modules, we thus fix the call to use the capabilities.json file from master,
which corresponds to the capabilities of a build of OPA with Wasm SDK enabled.

* docs/content/wasm.md: mention abi version, change headers

There is only one `#` header in a markdown document, so this fixes
that by adding a few `#`. I haven't added it everywhere below
`# Compiling`, but I think the structure is OK now.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2021-02-12 13:29:02 +01:00
Anders Eknert fa94a2d466 Add ceil and floor built-ins
Closes #2930

Signed-off-by: Anders Eknert <anders@eknert.com>
2021-01-07 16:22:41 -05:00
Jasper Van der Jeugt 1e17cc0c62 Add json.patch builtin
This is an implementation of the `json.patch` builtin.  Previous discussions
about this include #2839 and #2167.

It does not use an external dependency but rather implements [RFC 6902] directly
on AST terms.  This avoids a conversion to JSON as well as the dependency; and
as an added bonus we can make `json.patch` work for sets as well, covering the
full space of AST terms.

In my first implementation I used a mutable approach by first creating a deep
copy and then modifying it in-place.  However, this leads to issues with
the cached `hash` values in objects on the path.  I replaced this with an
implementation that creates shallow copies.  The performance tradeoff is that
smaller patches should be faster; but replacing parts will be slower.  Since we
don't know about too many people using this, I think both sides are acceptable.

I vendored the [json-patch-tests] into the test suite in a way that should
make updating them fairly easy.  I am also testing the cases disabled there
(since they do work for us!) but I disabled two test cases by adding a new
`opa_disabled` key.  These are:

 -  Us allowing `"foo"` as path (which should be `"/foo"` if you interpret
    the RFC strictly).
 -  A duplicate entry in the JSON patch object which isn't caught by OPA
    since it's consistent.

I added some additional tests for sets and things seem to work.  I'm going
to try out this new functionality in our larger codebase to see if any issues
come up, but I expect it to hold up.  Update: we've been using this builtin
and haven't seen any issues so far.

[RFC 6902]: https://tools.ietf.org/html/rfc6902#section-4.4
[json-patch-tests]: https://github.com/json-patch/json-patch-tests

Signed-off-by: Jasper Van der Jeugt <jasper@fugue.co>
2020-11-24 14:54:58 -05:00
Johannes Larsson 150bcaef07 encoding: hex.encode/decode and b64url.no_padding
The builtin hash algorithms hex encode the result. To use the hash functions
there must be possible to decode the value and re-encode it in the expected format.
This enables validation of x5t/x5t_s256, which are base64 url encoded hashes.

Fixes: #2849
Signed-off-by: Johannes Larsson <johannes.a.larsson@gmail.com>
2020-11-09 10:04:00 -05:00
Jasper Van der Jeugt d8947db27d topdown: add yaml.is_valid and json.is_valid
Signed-off-by: Jasper Van der Jeugt <jasper@fugue.co>
2020-10-27 10:30:15 -04:00
Ashutosh Narkar e9aee648b9 topdown: Add net.cidr_merge function to produce smallest possible list of CIDRs
This commit adds a new builtin to merge adjacent subnets and return the
smallest possible list of CIDRs.

To help with computing CIDR blocks between two
IP networks, an implemetation from https://github.com/cilium/cilium
is leveraged.

Fixes: #2692

Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
2020-09-24 18:03:23 -07:00
Calle Pettersson 36781fb203 topdown: Add base64.is_valid builtin
Adds a builtin to check if a string is valid base64

Fixes: #2690

Signed-off-by: Calle Pettersson <calle@cape.nu>
2020-09-22 10:11:44 -04:00
Frederic 870f28ff4a topdown: Add urlquery.decode_object builtin
This builtin is the reverse of the encode_object builtin and
makes it easier to use the URI query parameters in policies

Fixes #2647

Signed-off-by: Frederic <frederic.vanreet@icloud.com>
2020-08-31 11:55:15 -04:00
Torin Sandall 063f1f385d ast: Add capabilities struct and file generation
Signed-off-by: Torin Sandall <torinsandall@gmail.com>
2020-07-23 19:35:22 -04:00