Rego - Looping through a list of lists to check if they match given parameters - open-policy-agent

We currently can control which Kubernetes services of type "LoadBalancer" can be created.
ConstraintTemplate
spec:
crd:
spec:
names:
kind: K8sMetalLBServicesGK
validation:
openAPIV3Schema:
properties:
allowedLBServices:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8smetallbservicesgk
# Deny not whitelisted Service LB names
violation[{"msg": msg}] {
is_not_getting_deleted
is_type_loadbalancer
svc := input.review.object.metadata.name
satisfied := [good | pattern := input.parameters.allowedLBServices[_] ; good = glob.match(pattern, [], svc)]
not any(satisfied)
msg := sprintf("%v" service creation was denied by GateKeeper,[svc])
}
is_not_getting_deleted() = true {
input.review.operation != "DELETE"
not input.review.object.metadata.deletionTimestamp
}
is_type_loadbalancer() = true {
input.review.object.spec.type == "LoadBalancer"
}
Constraint
spec:
match:
excludedNamespaces: ["*-system"]
kinds:
- apiGroups: ["*"]
kinds: ["Service"]
parameters:
allowedLBServices:
- pakalu
We would like to extend this by controlling which service of type "LoadBalancer" can be created per namespace. For that, we would need to change the validation in the template and the rule logic. So constraint could look similar to this, if we want to allow services named "pakalu" and "papito" in namespace "ham", and allow services named "pakalu" and "foo" in namespace "spam".
spec:
match:
excludedNamespaces: ["*-system"]
kinds:
- apiGroups: ["*"]
kinds: ["Service"]
parameters:
allowedLBServices:
- ham:
- pakalu
- papito
- spam:
- pakalu
- foo

You could modify the constraint template to expect an object for allowedLBServices, and modify the policy to work with that:
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8smetallbservicesgk
spec:
crd:
spec:
names:
kind: K8sMetalLBServicesGK
validation:
openAPIV3Schema:
type: object
properties:
allowedLBServices:
type: object
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8smetallbservicesgk
# Deny not whitelisted Service LB names
violation[{"msg": msg}] {
is_not_getting_deleted
is_type_loadbalancer
not allowed_in_namespace
msg := sprintf("%v service creation was denied by GateKeeper", [input.review.object.metadata.name])
}
allowed_in_namespace {
namespace := input.review.object.metadata.namespace
pattern := input.parameters.allowedLBServices[namespace][_]
glob.match(pattern, [], input.review.object.metadata.name)
}
is_not_getting_deleted {
input.review.operation != "DELETE"
not input.review.object.metadata.deletionTimestamp
}
is_type_loadbalancer {
input.review.object.spec.type == "LoadBalancer"
}
And update the constraint to use an object as well:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sMetalLBServicesGK
metadata:
name: foo
spec:
match:
excludedNamespaces: ["*-system"]
kinds:
- apiGroups: ["*"]
kinds: ["Service"]
parameters:
allowedLBServices:
foo:
- pakalu
Nit: I'd use rules over functions, unless you want to provide arguments.

Related

How to define in swagger spec either of two properties are not null and required

I have a requirement to have two properties in the payload say property1 and propert2. Both are array type. Condition is, that either both can have values, or only one of them will be non-null. But both cannot be null at the same time.
How can I define this requirement in swagger spec, so that I can enforce schema such that both should not be null but any one of them can be null.
Valid Examples:
Ex1--> {"Property1": ["value1"], "Property2": ["value2"]}
Ex2--> {"Property2": ["value2"]}
Ex3--> {"Property1": ["value1"]}
Invalid Example:
{"Property1": [], "Property2": []}
Assuming the object can only contain these two properties (Property1 and/or Property2) and no other properties, your use case can be defined using a combination of object-level minProperties and array-level minItems:
MyObject:
type: object
minProperties: 1 # <-- requires at least one of Property1 or Property2 to be present
properties:
Property1:
type: array
items:
type: string
minItems: 1 # <-----
Property2:
type: array
items:
type: string
minItems: 1 # <-----
If the payload can also contain other properties, the "either Property1 or Property2 must be present" condition can be defined using anyOf + required. This requires OpenAPI 3.x:
# openapi: 3.x.x
MyObject:
type: object
anyOf: # <----------------
- required: [Property1]
- required: [Property2]
properties:
Property1:
type: array
items:
type: string
minItems: 1
Property2:
type: array
items:
type: string
minItems: 1
SomeOtherProperty:
...

How to make swagger examples component with allOf

components:
examples:
J:
value:
name: my name
J2:
value:
allOf:
- $ref: '#/components/examples/J'
- id: 123
Use J2 in response body examples:
responses:
200:
content:
application/json:
examples:
test:
$ref: '#/components/examples/J2'
Current result:
{
"$ref": "#/components/examples/J",
"id": 123
}
Expected result
{
"name": "my name",
"id": 123
}
Related:
https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
Swagger schema properties ignored when using $ref - why?
This is not supported.
The value is meant to be the literal value of the example. As a result, value does not support allOf and $ref. OpenAPI does not have a way to merge the values of multiple example components.
You cannot combine at example level but you can combine examples at path level.
This is invalid
components:
examples:
J:
value:
name: my name
J2:
value:
allOf:
- $ref: '#/components/examples/J'
- id: 123
But you can do this
components:
examples:
J:
value:
name: my name
J2:
value:
id: 123
content:
application/json:
schema:
$ref: '#/components/schemas/MyObject'
examples:
objectExample:
allOf :
- $ref: '#/components/examples/J'
- $ref: '#/components/examples/J2'

Error "bad indentation of a mapping entry" in Swagger editor

In the OpenAPI definition below, the parameter definition causes the parser error "bad indentation of a mapping entry". What is wrong? Error at "properties" (last 5th line)
responses:
'200':
description: OK
content:
application/json;charset=UTF-8:
schema:
type: object
properties:
creationTime:
type: string
format: date-time
description: >-
The date and time the response was created in GMT time
with a format of "MM-dd-yyyy HH:mm:ss.SSSZ"
example: '03-12-2019 14:05:35.182-0500'
response:
type: array
description: ''
properties:
recordID:
type: string
maxLength: 20
externalID:
The error occurs because the properties keyword is indented more than its sibling type and description keywords.
response:
type: array
description: ''
properties: # <---- Indentation doesn't match the sibling
# `type` and `description` keywords
However, type: array schemas need items, not properties.
It looks like the response key is either missing some keywords or has extra keywords. The correct definition for response would be:
response:
type: object # <----------
description: ''
properties: # <----------
recordID:
type: string
maxLength: 20
externalID:
...
or
response:
type: array
description: ''
items: # <----------
type: object # <----------
properties: # <----------
recordID:
type: string
maxLength: 20
externalID:
...
depending on what you need.

springdoc-openapi spec generation for inheritance witth generics

I have a Spring Boot (kotlin) project for which I use springdoc-openapi to generate OpenApi 3 spec. My data model looks like so:
open class Animal
data class Cat(val catName: String) : Animal()
data class Dog(val dogName: String) : Animal()
open class Food<T : Animal>
class CatFood : Food<Cat>()
class DogFood : Food<Dog>()
and a simple controller like so:
#GetMapping("/test")
fun test(): Food<out Animal> = DogFood()
for which the generated yaml is:
openapi: 3.0.1
info:
title: OpenAPI definition
version: v0
servers:
- url: http://localhost:8085
paths:
/test:
get:
tags:
- test-controller
operationId: test
responses:
"200":
description: default response
content:
'*/*':
schema:
$ref: '#/components/schemas/FoodAnimal'
components:
schemas:
FoodAnimal:
type: object
The problem here is that my controller can return either DogFood or CatFood, and that is specified in the return type. The schema I would expect to be generated is:
openapi: 3.0.1
info:
title: OpenAPI definition
version: v0
servers:
- url: http://localhost:8085
paths:
/test:
get:
tags:
- test-controller
operationId: test
responses:
"200":
description: default response
content:
'*/*':
schema:
oneOf:
- $ref: '#/components/schemas/FoodAnimal'
- $ref: '#/components/schemas/DogFood'
- $ref: '#/components/schemas/CatFood'
components:
schemas:
FoodAnimal:
type: object
CatFood:
allOf:
- $ref: '#/components/schemas/FoodAnimal'
type: object
DogFood:
allOf:
- $ref: '#/components/schemas/FoodAnimal'
type: object
Is there some way to achieve this?
For inheritance, you just need to add #Schema annotation, on your parent class:
#Schema(
type = "object",
title = "Food",
subTypes = [CatFood::class, DogFood::class]
)
open class Food<T : Animal>
class CatFood : Food<Cat>()
class DogFood : Food<Dog>()
If you need a reponse using oneOf, you will have to add #Response:
#GetMapping("/test")
#ApiResponse(content = [Content(mediaType = "*/*", schema = Schema(oneOf = [Food::class, CatFood::class,DogFood::class]))])
fun test(): Food<out Animal> = DogFood()
I had problems using OpenApi with inheritance for nested properties.
I used JsonSubtype annotations and generics as a workaround.
data class AnimalResponse<FoodResponse>(
val id: UUID,
val eats: FoodResponse
)
#JsonSubTypes(value = [
JsonSubTypes.Type(
value = CatFoodResponse::class,
name = "CAT_FOOD"
), JsonSubTypes.Type(
value = DogFoodResponse::class,
name = "DOG_FOOD"
)])
interface FoodResponse
This will show all types of FoodResponses in AnimalResponse Schema.

Model composition in swagger spec without additional nesting layer

If I have the following example where Settings definition is embedded in Thing via composition:
definitions:
Settings:
properties:
foobar:
type: number
format: double
boofar:
type: string
Thing:
properties:
allOf:
$ref: '#/definitions/Settings'
name:
type: string
If I define a method to POST a Thing in editor.swagger.io, it ends up constructing JSON that looks like this:
{
settings: {
foobar: 1,
boofar: "text here"
},
name: "some name"
}
I want to embed a model definition with composition but without the additional nested property definition -- is this possible? This is the JSON structure I would like to have for Thing:
{
foobar: 1,
boofar: "text here",
name: "some name"
}
Is there a way to achieve this?
Your example do not really use composition as allOf is a property.
allOf is supposed to be on the root of the definition and it's an array of schema (reference or inline).
Here's the proper way to use allOf for your example:
swagger: '2.0'
info:
title: API
version: 1.0.0
paths:
/thing:
get:
responses:
200:
description: OK
schema:
$ref: '#/definitions/Thing'
definitions:
Settings:
properties:
foobar:
type: number
format: double
boofar:
type: string
Thing:
allOf:
- $ref: '#/definitions/Settings'
- properties:
name:
type: string
Resulting rendering in SwaggerUI:

Resources