rego check if item in list exists in item in another list - open-policy-agent

We have an undertermined list of items in a resource that need to be checked in case they are using one of the deprecated given paramenters. In gatekeeper, the constraint with the parameters looks like this:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: SOMEKind
metadata:
name: somename
spec:
match:
kinds:
- apiGroups: ["networking.istio.io"]
kinds: ["EnvoyFilter"]
parameters:
envoyfilters:
- http_squash:
canonical: "envoy.filters.http.squash"
deprecated: "envoy.squash"
- listener_http_inspector:
canonical: "envoy.filters.listener.http_inspector"
deprecated: "envoy.listener.http_inspector"
The resource to check looks like this:
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: envoyfilter
spec:
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_OUTBOUND
listener:
filterChain:
filter:
name: envoy.listener.http_inspector
subFilter:
name: envoy.filters.http.router
We would need to make sure than for any .spec.configPatches[].match.listener.filterChain.filter.name that could exist in the analyzed resource, if that name matches any of the given "parameters.envoyfilters[].deprecated given, a violation would happen.
Due to constraints, we are unable to update opa/gk at the moment so we can't use the "import future.keywords".
The reason for having the "canonical" in the list, is because we would like to provide the "good" alertnative fitler to use in the msg of the violation.
We are trying different approaches but this is the latest (not working) one:
violation[{"msg": msg}] {
config_patches := input.review.object.spec.configPatches[match][listener][filterchain][filter][_].name
deprecated_envoyfilters := input.parameters.envoyfilters
use_deprecated_envoyfilters(config_patches,deprecated_envoyfilters)
msg := sprintf("REVIEW OBJECT: %v", [config_patches])
}
contains(filters, filter) {
filters[_] = filter
}
use_deprecated_envoyfilters(config_patches,deprecated_envoyfilters) = true {
counter := [ef | efs := config_patches ; contains(efs,deprecated_envoyfilters[_].deprecated) ]
count(counter) > 0

First of, did you really mean these parameters?
parameters:
envoyfilters:
- http_squash:
canonical: "envoy.filters.http.squash"
deprecated: "envoy.squash"
- listener_http_inspector:
canonical: "envoy.filters.listener.http_inspector"
deprecated: "envoy.listener.http_inspector"
because they translate to, for example,
{
"http_squash": null,
"canonical": "envoy.filters.http.squash",
"deprecated": "envoy.squash"
},
So I think some key/val structure is nicer to work with here, let's go with:
envoyfilters:
http_squash:
canonical: "envoy.filters.http.squash"
deprecated: "envoy.squash"
listener_http_inspector:
canonical: "envoy.filters.listener.http_inspector"
deprecated: "envoy.listener.http_inspector"
Having converted your yaml to the GK input via kube-review, and adding the parameters to the input, I've put up this example on the playground: https://play.openpolicyagent.org/p/o8QrD3xj1y
It boils down to this:
package play
violation[{"msg": msg}] {
config_patch := input.review.object.spec.configPatches[match][listener][filterchain][filter][_].name
some depr_name # name of deprecation
input.parameters.envoyfilters[depr_name].deprecated == config_patch
better := input.parameters.envoyfilters[depr_name].canonical
msg := sprintf("REVIEW OBJECT: %s, violation %s, use %s instead", [config_patch, depr_name, better])
}
Looping over the different config patches happens implicitly by Rego trying to satisfy
config_patch := input.review.object.spec.configPatches[match][listener][filterchain][filter][_].name
and then we need to look up in our parameters if one of the to-be-applied patches is deprecated:
some depr_name # name of deprecation
input.parameters.envoyfilters[depr_name].deprecated == config_patch
better := input.parameters.envoyfilters[depr_name].canonical

When you use a looping construct like deprecated := input.parameters.envoyfilters[_].deprecated, it does not return a list, but assigns the deprecated variable to each item matching the expression. Your policy could thus be simplified to just:
violation[{"msg": msg}] {
config_patch := input.review.object.spec.configPatches[match][listener][filterchain][filter][_].name
deprecated := input.parameters.envoyfilters[_].deprecated
config_patch == deprecated
msg := sprintf("deprecated config patch: %v", [config_patch])
}
Full example on the Rego Playground.

Related

Make Custom SwiftLint action regex ignore comments

I have a custom SwiftLint action to flag up print() statements:
custom_rules:
disable_print:
included: ".*\\.swift"
name: "print usage"
regex: "((\\bprint)|(Swift\\.print))\\s*\\("
message: "Don't use print"
severity: error
It works but it also flags whenever I have a print() statement specified in a documentation comment like this:
/// some comment mentioning print("hello") <- Error here
func myFunc() {}
How can I alter the regex so it ignores the print statement whenever it's in a documentation comment?
It seems that a custom rule can specify what type of code will match. The property is called match_kinds, example from Swiftlint Readme:
match_kinds: # SyntaxKinds to match. optional.
- comment
- identifier
Specifying identifier should be enough for your use case.

How to Whitelist a Container using the Open Policy Agent Gatekeeper K8sPSPCapabilities Constraint Template

I'd like to whitelist a container in the K8sPSPCapabilities constraint template but am having some difficulty with the rego language. I'd like to disallow the NET_RAW capability for all containers except a specific container. Would appreciate it if someone could point me in the right direction.
I believe you're referring to this template from the Gatekeeper ConstraintTemplate library, which is daunting. Unfortunately as written that ConstraintTemplate doesn't allow deny listing specific allowedCapabilities. Let's construct it.
If you're just looking for the Rego, here's it in the Rego Playground.
Validating a Container
We'll start from the inside out. First, let's assume we have a Container and want to ensure it does not have NET_RAW in .securityContext.capabilities.add. First, we'll collect the values of this list into a set.
capabilities := {c | c := container.securityContext.capabilities.add[_]}
This is called a set comprehension.
Since it's quite possible that you'll want to deny multiple capabilities, we'll assume you'll want to set this as a list parameter rather than having it hard-coded in your ConstraintTemplate. We need to convert this list from input.parameters into a set as well.
denied := {c | c := input.parameters.deniedCapabilities[_]}
What we want to do now is construct the set intersection between the capabilities in the Container and those in denied.
count(capabilities & denied) > 0
This returns true if there are any capabilities in both the container and in the list of denied capabilities.
We must consider the case where the Container specifies '*', which would implicitly include NET_RAW but not be matched by our logic above. We check this case with:
capabilities['*']
Note that every truthy statement in a Rego function is implicitly AND-ed together. So the following would check both conditions, but we want to match either:
count(capabilities & denied) > 0
capabilities['*']
We can do this by providing two definitions for has_disallowed_capabilities:
has_disallowed_capabilities(container) {
capabilities := {c | c := container.securityContext.capabilities.add[_]}
denied := {c | c := input.parameters.deniedCapabilities[_]}
count(capabilities & denied) > 0
}
has_disallowed_capabilities(container) {
capabilities := {c | c := container.securityContext.capabilities.add[_]}
capabilities["*"]
}
If we call has_disallowed_capabilities on a Container, Rego will automatically check both definitions and return true if at least one returns true.
Validating a Pod
Now we'll actually write the violation functions idiomatic to Gatekeeper.
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
has_disallowed_capabilities(container)
msg := sprintf("container <%v> has a disallowed capability. Denied capabilities are %v", [container.name, input.parameters.deniedCapabilities])
}
This violation first creates an iterator, container, which will iterate over all Containers in the Pod. By calling a function on the iterator, we implicitly call that function on every Container in the iterator.
We must also check initContainers as those may also have the NET_RAW capability. It looks near-identical to the above:
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
has_disallowed_capabilities(container)
msg := sprintf("initContainer <%v> has a disallowed capability. Denied capabilities are %v", [container.name, input.parameters.deniedCapabilities])
}
Whitelisting a Container
Almost done. As you said above, we want to whitelist a specific container. Recall that all statements in a Rego function are AND-ed together, and the violation function must return true in order for the Container to fail validation. So if we create a statement that evaluates to false if it matches the Container we want, we're done!
Let's say we want to ignore Containers with the name: abc. We can match all other containers with:
container.name != "abc"
This just needs to go in both of our violation functions, and we'll be all set!
Putting everything together
Finally, we put all that we've created above into a ConstraintTemplate:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8spspdeniedcapabilities
annotations:
description: Denies Pod capabilities.
spec:
crd:
spec:
names:
kind: K8sPSPDeniedCapabilities
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
deniedCapabilities:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package capabilities
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.name != "abc"
has_disallowed_capabilities(container)
msg := sprintf("container <%v> has a disallowed capability. Denied capabilities are %v", [container.name, input.parameters.deniedCapabilities])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
container.name != "abc"
has_disallowed_capabilities(container)
msg := sprintf("initContainer <%v> has a disallowed capability. Denied capabilities are %v", [container.name, input.parameters.deniedCapabilities])
}
has_disallowed_capabilities(container) {
capabilities := {c | c := container.securityContext.capabilities.add[_]}
denied := {c | c := input.parameters.deniedCapabilities[_]}
count(capabilities & denied) > 0
}
has_disallowed_capabilities(container) {
capabilities := {c | c := container.securityContext.capabilities.add[_]}
capabilities["*"]
}
And a Constraint to instantiate it:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPDeniedCapabilities
metadata:
name: denied-capabilities
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
deniedCapabilities: ["NET_RAW"]
If instead you want the whitelisted containers to be configurable, you would follow a similar process to the one we used to make deniedCapabilities.

How do i add Input transformation to a target using aws cdk for a cloudwatch event rule?

After i create a cloud-watch event rule i am trying to add a target to it but i am unable to add a input transformation. Previously the add target had the props allowed for input transformation but it does not anymore.
codeBuildRule.addTarget(new SnsTopic(props.topic));
The aws cdk page provides this solution but i dont exactly understand what it says
You can add additional targets, with optional input transformer using eventRule.addTarget(target[, input]). For example, we can add a SNS topic target which formats a human-readable message for the commit.
You should specify the message prop and use RuleTargetInput static methods. Some of these methods can use strings returned by EventField.fromPath():
// From a path
codeBuildRule.addTarget(new SnsTopic(props.topic, {
message: events.RuleTargetInput.fromEventPath('$.detail')
}));
// Custom object
codeBuildRule.addTarget(new SnsTopic(props.topic, {
message: RuleTargetInput.fromObject({
foo: EventField.fromPath('$.detail.bar')
})
}));
I had the same question trying to implement this tutorial in CDK: Tutorial: Set up a CloudWatch Events rule to receive email notifications for pipeline state changes
I found this helpful as well: Detect and react to changes in pipeline state with Amazon CloudWatch Events
NOTE: I could not get it to work using the Pipeline's class method onStateChange().
I ended up writing a Rule:
const topic = new Topic(this, 'topic', {topicName: 'codepipeline-notes-failure',
});
const description = `Generated by the CDK for stack: ${this.stackName}`;
new Rule(this, 'failed', {
description: description,
eventPattern: {
detail: {state: ['FAILED'], pipeline: ['notes']},
detailType: ['CodePipeline Pipeline Execution State Change'],
source: ['aws.codepipeline'],
},
targets: [
new SnsTopic(topic, {
message: RuleTargetInput.fromText(
`The Pipeline '${EventField.fromPath('$.detail.pipeline')}' has ${EventField.fromPath(
'$.detail.state',
)}`,
),
}),
],
});
After implementing, if you navigate to Amazon EventBridge -> Rules, then select the rule, then select the Target(s) and then click View Details you will see the Target Details with the Input transformer & InputTemplate.
Input transformer:
{"InputPathsMap":{"detail-pipeline":"$.detail.pipeline","detail-state":"$.detail.state"},"InputTemplate":"\"The
Pipeline '<detail-pipeline>' has <detail-state>\""}
This would work for CDK Python. CodeBuild to SNS notifications.
sns_topic = sns.Topic(...)
codebuild_project = codebuild.Project(...)
sns_topic.grant_publish(codebuild_project)
codebuild_project.on_build_failed(
f'rule-on-failed',
target=events_targets.SnsTopic(
sns_topic,
message=events.RuleTargetInput.from_multiline_text(
f"""
Name: {events.EventField.from_path('$.detail.project-name')}
State: {events.EventField.from_path('$.detail.build-status')}
Build: {events.EventField.from_path('$.detail.build-id')}
Account: {events.EventField.from_path('$.account')}
"""
)
)
)
Credits to #pruthvi-raj comment on an answer above

CDK generating empty targets for CfnCrawler

I'm using CDK Python API to define a Glue crawler, however, the CDK generated template contains empty 'Targets' block in the Crawler resource.
I've not been able to find an example to emulate. I've tried varying the definition of the targets object, but the object definition seems to be ignored by CDK.
from aws_cdk import cdk
BUCKET='poc-1-bucket43879c71-5uabw2rni0cp'
class PocStack(cdk.Stack):
def __init__(self, app: cdk.App, id: str, **kwargs) -> None:
super().__init__(app, id)
from aws_cdk import (
aws_iam as iam,
aws_glue as glue,
cdk
)
glue_role = iam.Role(
self, 'glue_role',
assumed_by=iam.ServicePrincipal('glue.amazonaws.com'),
managed_policy_arns=['arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole']
)
glue_crawler = glue.CfnCrawler(
self, 'glue_crawler',
database_name='db',
role=glue_role.role_arn,
targets={"S3Targets": [{"Path": f'{BUCKET}/path/'}]},
)
I expect the generated template to contain a valid 'targets' block with a single S3Target. However, cdk synth outputs a template with empty Targets in the AWS::Glue::Crawler resource:
gluecrawler:
Type: AWS::Glue::Crawler
Properties:
DatabaseName: db
Role:
Fn::GetAtt:
- glueroleFCCAEB57
- Arn
Targets: {}
Resolved, thanks to a clever colleague!
Changing "S3Targets" to "s3Targets", and "Path" to "path" resolved the issue. See below.
Hi Bob,
When I use typescript, the following works for me:
new glue.CfnCrawler(this, 'glue_crawler', {
databaseName: 'db',
role: glue_role.roleArn,
targets: {
s3Targets: [{ path: "path" }]
}
}
When I used Python, the following appears working too:
glue_crawler = glue.CfnCrawler(
self, 'glue_crawler',
database_name='db',
role=glue_role.role_arn,
targets={
"s3Targets": [{ "path": f'{BUCKET}/path/'}]
},
)
In Typescript, TargetsProperty is an interface with s3Targets as a property. And in
s3Targets, path is a property as well. I guess during the JSII transformation, it forces
us to use the same names in Python instead of the initial CFN resource names.
A more general way to approach this problem is to dig inside the cdk library in 2 steps:
1.
from aws_cdk import aws_glue
print(aws_glue.__file__)
(.env/lib/python3.8/site-packages/aws_cdk/aws_glue/__init__.py)
Go to that file and see how the mapping/types are defined. As of 16 Aug 2020, you find
#jsii.data_type(
jsii_type="#aws-cdk/aws-glue.CfnCrawler.TargetsProperty",
jsii_struct_bases=[],
name_mapping={
"catalog_targets": "catalogTargets",
"dynamo_db_targets": "dynamoDbTargets",
"jdbc_targets": "jdbcTargets",
"s3_targets": "s3Targets",
}
)
I found that the lowerCamelCase always work, while the pythonic snake_case does not.

How can I create a shared library in Jenkins with both arguments and a body?

Basically I want to create a shared library in Jenkins which turns:
kPod(label: label){
body
}
into:
podTemplate(label: label, //use argument as label
containers: [containerTemplate(name: 'jnlp', image: 'someImage', args: '${computer.jnlpmac} ${computer.name}')],
volumes: [hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock')]) {
//put body here
}
However all the examples I see either allow you to access the arguments OR the body, but not both. The doc has no mention of it either: https://jenkins.io/doc/book/pipeline/shared-libraries/
But it clearly is possible since the podTemplate step itself is doing what I want.
Lol, I just needed to add it as a param:
def call(config, body) {
// config is a map of the parameters
}

Resources