Skip to content

rego: Upgrade to OPA v1#2815

Open
micromaomao wants to merge 24 commits into
microsoft:mainfrom
micromaomao:tingmao_github/opa
Open

rego: Upgrade to OPA v1#2815
micromaomao wants to merge 24 commits into
microsoft:mainfrom
micromaomao:tingmao_github/opa

Conversation

@micromaomao

@micromaomao micromaomao commented Jul 10, 2026

Copy link
Copy Markdown
Member
  • Bump OPA dependency
  • Change imports to use v1
  • Fix framework.rego

Depends-on: #2789
Depends-on: #2792

@micromaomao micromaomao requested a review from a team as a code owner July 10, 2026 14:38
@jterry75

Copy link
Copy Markdown
Contributor

@micromaomao - I didn't realize you were working on this. Could we first merge my change and then we can take your updates to move to v1?

In implementing the fragment parameters feature, we need a way to store, for
each issuer and feed tuple, which parameters should be used for fragments
matching it.  Consider this list of fragments, which a parent fragment might
define:

fragments := [
    {
        "feed": "mcr.microsoft.com/maa/enclavehost",
        "issuer": "did:x509:0:sha256:...",
        "includes": [
            "containers",
            "fragments"
        ],
        "minimum_svn": "1",
        "parameters": {
            "region": "australiacentral2",
            "cloud": "Public"
        }
    },
    // ...
]

When we load the fragment containing this, we need to iterate through its
data.<namespace>.fragments array, and for each entry, append the parameters
object to an array that is keyed by the issuer and feed, since we can have
multiple such fragment import entries for the same issuer and feed, but with
different parameters.

This basically means that we need the equivalent of the following code in Rego,
which I've failed to come up with a way to write:

    for f in fragment_fragments {
        issuer = data.metadata.issuers[f.issuer] or {}
        feed = issuer.feeds[f.feed] or {}
        feed.parameters = feed.parameters or []
        feed.parameters = array_append(feed.parameters, f.parameters)
        issuer.feeds[f.feed] = feed
        data.metadata.issuers[f.issuer] = issuer
    }

While we can dynamically lookup or store based on a key that is from a variable
access, and we can send multiple metadata updates, those updates cannot express
the semantic of "merging" objects or arrays.

To make this simpler, we introduce a new metadata data type - "set", which
supports storing an unordered list of arbitrary objects (which themselves might
be either a string, or an object containing key-value pairs), that can be
inserted to via metadata operations one at a time.  This means that we can
simply append to the metadata update operations array once per each fragment
import statement, and the outcome would be the union of all the parameter
objects.  This is also very easy to query in Rego, given an issuer and feed:

    parameters := [
        fp.parameters |
            fp = data.metadata.fragment_parameters[_]
            fp.issuer == input.issuer
            fp.feed == input.feed
    ]

It is likely that the existing code that extracts included containers/fragments
from a fragment can be simplified by using this feature, but that is outside of
the scope of this PR.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
…and improve comments

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
…ching strategies

This is to make it easier to parameterize environment rules.

Currently, name and value for an environment rule are actually combined into one
"pattern" field, and there is only one strategy for the combined pattern.  This
presents a problem when a fragment wants to delegate the decision of e.g.
whether to match the value (but only the value, not the key) with a regex or
with a fixed string.  We split "pattern" and "strategy" out into "name",
"name_strategy", "value" and "value_strategy" in order to allow more flexibility
when fragment exposes env-var parameters.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Also fix wrong assertDecisionJSONContains - the original code for
Test_Rego_EnforceEnvironmentVariablePolicy_NotAllMatches(_Windows)? only asserts
that a key that is valid exists in the decision JSON, but this is incorrect
because it should had verified that the decision JSON mentions the invalid key.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
This commit implements the support for the "parameters" feature for fragments.
This is achieved by having the framework extract the parameters object from
fragment import statements, and storing them in a set, tagged with the
applicable (issuer, feed) pair.  On future fragment loads, if the fragment's
issuer and feed pair matches with any previous entry from this set, the
parameters will be passed to the fragment via an automatically injected object
added to the end of the fragment's Rego source (__fragment_parameters).

(Note that in reality, we need to combine this set with the import statements
defined in the main policy. c.f. candidate_fragments.  This is done in
fragment_parameters_for)

In order for fragments to use this parameter object, it is expected that all
fragments will now have an additional "stub" inserted at runtime to define the
parameter() function, which will, under the hood, reference a "hidden" variable
__fragment_parameters, also inserted dynamically at runtime, containing the
actual parameter values.

If multiple fragment parameters are specified, all combinations of values are
considered "allowed", and therefore the fragment injection is repeated, passing
in different parameter combinations, one for each combination.  This means that
if a fragment defines, for example:

	containers := [
		{
			...
			"env_rules": [
				{
					"name": "SOME_KEY",
					"name_strategy": "string",
					"value": parameter("my_param"),
					"value_strategy": "string"
				}
			]
		}
	]

and we have two fragment import statement for this fragment, e.g.:

fragments := [
	{
		"feed": ...,
		"issuer": ...,
		"minimum_svn": ...,
		"parameters": {
			"my_param": "value1"
		}
	},
	{
		"feed": ...,
		"issuer": ...,
		"minimum_svn": ...,
		"parameters": {
			"my_param": "value2"
		}
	}
]

Then the container can be started with either SOME_KEY=value1 or
SOME_KEY=value2.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
---

Changes:
- Fix missing [_]
- fix wrong usage of defer
- Inject fragment parameter function definitions at runtime:

  We must inject this at runtime so as to avoid having to support this exact
  implementation (eg __fragment_parameters[name]) of fetching the parameters
  forever (as it will essentially be hard-coded in the generated policy).

- Move the actual implementation of parameter() into the framework so that
  fragments doesn't have to say 'import future.keywords.every', 'import
  future.keywords.in'.

- Use indirection with default when accessing fragment.parameters to not break
  older fragments.

  If we don't do this, loading a fragment without the parameters definition
  would fail due to "unsafe" rego variable references.

- Fix broken tests caused by the new extract_parameter framework function

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
…ing env list

If every element in the input env list matches some env rule in some container,
but there is no container that matches the entire env list, we currently deny
correctly but returns no error message.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
This will test for scenarios like different fragments using the same parameter
name, multiple parameter combinations, correct parameter passing for nested
fragments, and make sure container creation is denied if the parameter and the
given environment / command doesn't match.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Name still provisional, might change later.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
…ching strategies

This is to make it easier to parameterize environment rules.

Currently, name and value for an environment rule are actually combined into one
"pattern" field, and there is only one strategy for the combined pattern.  This
presents a problem when a fragment wants to delegate the decision of e.g.
whether to match the value (but only the value, not the key) with a regex or
with a fixed string.  We split "pattern" and "strategy" out into "name",
"name_strategy", "value" and "value_strategy" in order to allow more flexibility
when fragment exposes env-var parameters.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Also fix wrong assertDecisionJSONContains - the original code for
Test_Rego_EnforceEnvironmentVariablePolicy_NotAllMatches(_Windows)? only asserts
that a key that is valid exists in the decision JSON, but this is incorrect
because it should had verified that the decision JSON mentions the invalid key.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
…ired rules present

This refactors rule_ok (and renames it) to fix the `some env in envList` being
applied at the wrong level.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
This currently support containers[_].env_rules and containers[_].mounts.  If
multiple platform_rules are defined, a container matching either one can be
started (but in a consistent manner - e.g. if two platforms have different
environment variables or mounts, a container can't "mix and match" between
them).

In order to achieve the above consistency, we "patch" the container objects
instead of adding logic to e.g. env_rule_ok or envList_ok.  This also means that
the error_objects of a denial message will reflect the platform rules inserted.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
…e field

Currently we only add input.rule to the original input, not the redacted input.
This results in the case of create_container not having the "rule" field in the
final deny message, but other enforcement points do have it since in those cases
the redactSensitiveData return the original input map.

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
# Conflicts:
#	pkg/securitypolicy/framework.rego
#	pkg/securitypolicy/regopolicy_linux_test.go
Revert some "rego.SetRegoVersion(ast.RegoV0)"
Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
go: upgraded github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 => v4.4.1
go: upgraded github.com/goccy/go-json v0.10.3 => v0.10.6
go: upgraded github.com/lestrrat-go/blackmagic v1.0.2 => v1.0.4
go: upgraded github.com/open-policy-agent/opa v1.4.0 => v1.18.2
go: upgraded github.com/prometheus/common v0.66.1 => v0.67.5
go: upgraded github.com/prometheus/procfs v0.16.1 => v0.20.1
go: upgraded github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 => v0.0.0-20250401214520-65e299d6c5c9
go: upgraded go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 => v0.69.0
go: upgraded go.opentelemetry.io/otel v1.43.0 => v1.44.0
go: upgraded go.opentelemetry.io/otel/metric v1.43.0 => v1.44.0
go: upgraded go.opentelemetry.io/otel/sdk v1.43.0 => v1.44.0
go: upgraded go.opentelemetry.io/otel/trace v1.43.0 => v1.44.0
go: upgraded google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 => v0.0.0-20260526163538-3dc84a4a5aaa
go: upgraded google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 => v0.0.0-20260526163538-3dc84a4a5aaa

Signed-off-by: Tingmao Wang <tingmaowang@microsoft.com>
Replacing
(^\w+(\(.+\)|\[(\w+|".+")\])?( := (\w+?|\{(.|\n)+?\}|\w+\[\w+\]))?) \{
with
$1 if {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants