diff --git a/README.md b/README.md index c99319c1..92ac40c9 100644 --- a/README.md +++ b/README.md @@ -1,176 +1,48 @@ -# Lula - The Kubernetes Compliance Engine +# Lula - The Cloud-Native Compliance Engine [![Go version](https://img.shields.io/github/go-mod/go-version/defenseunicorns/lula?filename=go.mod)](https://go.dev/) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/defenseunicorns/lula/badge)](https://api.securityscorecards.dev/projects/github.com/defenseunicorns/lula) -Lula is a tool written to bridge the gap between expected configuration required for compliance and **_actual_** configuration. +lula logo -Cloud Native Infrastructure, Platforms, and applications can establish OSCAL documents that live beside source-of-truth code bases. Providing an inheritance model for when a control that the technology can satisfy _IS_ satisfied in a live-environment. +Lula is a tool designed to bridge the gap between expected configuration required for compliance and **_actual_** configuration. -This can be well established and regulated standards such as NIST 800-53. It can also be best practices, Enterprise Standards, or simply team development standards that need to be continuously monitored and validated. +### Key Features +* **Assess** compliance of a system against user-defined controls +* **Evaluate** an evolving system for compliance _over time_ +* **Generate** machine-readible OSCAL artifacts +* **Accelerate** the compliance and accreditation process -## Why this approach vs a policy engine? +### Why Lula is different than a standard policy engine +* Lula is not meant to compete with policy engines - rather augment the auditing and alerting process +* Often admission control processes have a difficult time establishing `big picture` global context control satisfaction, Lula fills this gap +* Lula is meant to allow modularity and inheritance of controls based upon the components of the system you build -- Lula is not meant to compete with policy engines - rather augment the auditing and alerting process -- Often admission control processes have a difficult time establishing `big picture` global context control satisfaction -- Lula is meant to allow modularity and inheritance of controls based upon the components of the system you build +## Overview -## How does it work? +Cloud-Native Infrastructure, Platforms, and Applications can establish [OSCAL documents](https://pages.nist.gov/OSCAL/about/) that are maintained alongside source-of-truth code bases. These documents provide an inheritance model to prove when a control that the technology can satisfy _IS_ satisfied in a live-environment. -Under the hood, Lula has two primary capabilities; Provider and Domains. +These controls can be well established and regulated standards such as NIST 800-53. They can also be best practices, Enterprise Standards, or simply team development standards that need to be continuously monitored and validated. -- A Domain is an identifier for where to collect data to be validated -- A Provider is the "engine" performing the validation using policy and the data collected. +Lula operates on a framework of proof by adding custom overlays mapped to the these controls, [`Lula Validations`](link), to measure system compliance. These `Validations` are constructed by establishing the collection of measurements about a system, given by the specified **Domain**, and the evaluation of adherence, performed by the **Provider**. -In the standard CLI workflow: +### Providers and Domains -- Target a `Component-Definition` OSCAL file for validation - - `lula validate oscal-component.yaml` -- This creates an object in memory for the OSCAL content -- Lula then traverses as required to identify `implemented-requirements` that contain a Lula Validation Payload -- When the payload has been identified: - - Lula processes provider to understand which provider to use for validation - - More than one provider can be used in an OSCAL document - - Lula processes the domain to understand how data is collected (and which data to collect) - - Lula collects the data for validation as specified in the payload - - Lula performs validation of the data collected as specified as policy in the payload +**Domain** is the identifier for where and which data to collect as "evidence". Below are the active and planned domains: + +| Domain | Current | Roadmap | +|----------|----------|----------| +| [Kubernetes](./docs/reference/domains/kubernetes-domain.md) | ✅ | - | +| [API](./docs/reference/domains/api-domain.md) | ✅ | - | +| Cloud Infrastructure | ❌ | ✅ | + +**Provider** is the "engine" performing the validation using policy and the data collected. Below are the active providers: + +| Provider | Current | Roadmap | +|----------|----------|----------| +| [OPA](./docs/reference/provideres/opa-provider.md) | ✅ | - | +| [Kyverno](./docs/reference/provideres/kyverno-provider.md) | ✅ | - | ## Getting Started -### Try it out - -#### Dependencies - -- A running Kubernetes cluster - - Kind - - `kind create cluster -n lula-test` - - K3d - - `k3d cluster create lula-test` -- kubectl -- GoLang version 1.22.x - -#### Steps - -1. Clone the repository to your local machine and change into the `lula` directory - - ```shell - git clone https://github.com/defenseunicorns/lula.git && cd lula - ``` - -2. While in the `lula` directory, compile the tool into an executable binary. This outputs the `lula` binary to the `bin` directory. - - ```shell - make build - ``` - -3. Apply the `./demo/namespace.yaml` file to create a namespace for the demo - - ```shell - kubectl apply -f ./demo/namespace.yaml - ``` - -4. Apply the `./demo/pod.fail.yaml` to create a pod in your cluster - - ```shell - kubectl apply -f ./demo/pod.fail.yaml - ``` - -5. Run the following command in the `lula` directory: - - ```shell - ./bin/lula validate -f ./demo/oscal-component.yaml - ``` - - The output in your terminal should inform you that the control validated is `not-satisfied`: - - ```shell - NOTE Saving log file to - /var/folders/f7/8csz3jj97lb8nqp_zv9kh07m0000gn/T/lula-2024-01-24-13-51-58-2247835644.log - • UUID: c759a19b-d408-424c-8342-298f45e18b68 - • Status: not-satisfied - ✔ Validating Implemented Requirement - 42C2FFDC-5F05-44DF-A67F-EEC8660AEFFD - • Writing Security Assessment Results to: assessment-results-01-24-2024-13:51:58.yaml - ``` - - This will also produce an assessment-results file with timestamp - review the findings and observations: - - ```yaml - findings: - - description: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - related-observations: - - observation-uuid: ef12a3bb-fd86-4336-9d28-98d00c7dc26d - target: - status: - state: not-satisfied - target-id: ID-1 - type: objective-id - title: 'Validation Result - Component:A9D5204C-7E5B-4C43-BD49-34DF759B9F04 / Control Implementation: A584FEDC-8CEA-4B0C-9F07-85C2C4AE751A / Control: ID-1' - uuid: c759a19b-d408-424c-8342-298f45e18b68 - observations: - - collected: "2024-01-24T13:51:58-08:00" - description: | - [TEST] ID-1 - a7377430-2328-4dc4-a9e2-b3f31dc1dff9 - methods: - - TEST - relevant-evidence: - - description: | - Result: not-satisfied - Passing Resources: 0 - Failing Resources 1 - uuid: ef12a3bb-fd86-4336-9d28-98d00c7dc26d - ``` - -6. Now, apply the `./demo/pod.pass.yaml` file to your cluster to configure the pod to pass compliance validation: - - ```shell - kubectl apply -f ./demo/pod.pass.yaml - ``` - -7. Run the following command in the `lula` directory: - - ```shell - ./bin/lula validate -f ./demo/oscal-component.yaml - ``` - - The output should now show the pod as passing the compliance requirement: - - ```shell - NOTE Saving log file to - /var/folders/f7/8csz3jj97lb8nqp_zv9kh07m0000gn/T/lula-2024-01-24-13-54-19-2423960428.log - • UUID: c3e4ccb2-6843-4ec2-a500-559cdd7918d5 - • Status: satisfied - ✔ Validating Implemented Requirement - 42C2FFDC-5F05-44DF-A67F-EEC8660AEFFD - • Writing Security Assessment Results to: assessment-results-01-24-2024-13:54:19.yaml - ``` - - This will produce a new assessment-results file with timestamp - review the findings and observations: - - ```yaml - findings: - - description: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - related-observations: - - observation-uuid: 84a169a1-74d6-4e26-bbfb-4dfc474c7790 - target: - status: - state: satisfied - target-id: ID-1 - type: objective-id - title: 'Validation Result - Component:A9D5204C-7E5B-4C43-BD49-34DF759B9F04 / Control Implementation: A584FEDC-8CEA-4B0C-9F07-85C2C4AE751A / Control: ID-1' - uuid: c3e4ccb2-6843-4ec2-a500-559cdd7918d5 - observations: - - collected: "2024-01-24T13:54:19-08:00" - description: | - [TEST] ID-1 - a7377430-2328-4dc4-a9e2-b3f31dc1dff9 - methods: - - TEST - relevant-evidence: - - description: | - Result: satisfied - Passing Resources: 1 - Failing Resources 0 - uuid: 84a169a1-74d6-4e26-bbfb-4dfc474c7790 - ``` - -## Future Extensibility - -- Support for cloud infrastructure state queries - -## Developing - -- Go 1.22.x +[Install Lula](./docs/getting-started/README.md) and check out the [Simple Demo](./docs/getting-started/simple-demo.md) to get familiar with Lula's `validate` and `evaluate` workflow to assess system compliance and establish thresholds. See the other tutorials for more advanced Lula use cases and information on how to develop your own `Lula Validations`! diff --git a/demo/develop-validation/oscal-component.yaml b/demo/develop-validation/oscal-component.yaml new file mode 100644 index 00000000..a3c1f03d --- /dev/null +++ b/demo/develop-validation/oscal-component.yaml @@ -0,0 +1,29 @@ +component-definition: + uuid: a506014d-cb8a-4db9-ac48-ef72f7209a60 + metadata: + last-modified: 2024-07-11T13:38:09.633174-04:00 + oscal-version: 1.1.2 + published: 2024-07-11T13:38:09.633174-04:00 + remarks: Lula Generated Component Definition + title: Component Title + version: 0.0.1 + components: + - uuid: 75859c1e-30f5-4fde-9ad4-c79f863b049f + type: software + title: podinfo + description: Sample application + control-implementations: + - uuid: a3039927-839c-5745-ac4e-a9993bcd60ed + source: https://github.com/defenseunicorns/lula + description: Control Implementation Description + implemented-requirements: + - uuid: 257d2b2a-fda7-49c5-9a2b-acdc995bc8e5 + control-id: ID-1 + description: >- + Podinfo, a sample application, is deployed into the cluster and exposed for testing purposes. + remarks: >- + System implements test application as target for development purposes. + links: + - href: 'file:./validation.yaml' + rel: lula + text: Check that Podinfo is healthy \ No newline at end of file diff --git a/demo/develop-validation/resources-bad.json b/demo/develop-validation/resources-bad.json new file mode 100644 index 00000000..6d518c64 --- /dev/null +++ b/demo/develop-validation/resources-bad.json @@ -0,0 +1,177 @@ +{ + "podinfoDeployment": { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "1", + "meta.helm.sh/release-name": "my-release", + "meta.helm.sh/release-namespace": "podinfo" + }, + "creationTimestamp": "2024-07-11T17:36:53Z", + "generation": 1, + "labels": { + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "my-release-podinfo", + "app.kubernetes.io/version": "6.7.0", + "helm.sh/chart": "podinfo-6.7.0" + }, + "name": "my-release-podinfo", + "namespace": "podinfo", + "resourceVersion": "1445826", + "uid": "443f38fa-9f7a-4df0-9fe9-098561c0ba77" + }, + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "my-release-podinfo" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": "25%", + "maxUnavailable": 1 + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "annotations": { + "prometheus.io/port": "9898", + "prometheus.io/scrape": "true" + }, + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/name": "my-release-podinfo" + } + }, + "spec": { + "containers": [ + { + "command": [ + "./podinfo", + "--port=9898", + "--cert-path=/data/cert", + "--port-metrics=9797", + "--grpc-port=9999", + "--grpc-service-name=podinfo", + "--level=info", + "--random-delay=false", + "--random-error=false" + ], + "env": [ + { + "name": "PODINFO_UI_COLOR", + "value": "#34577c" + } + ], + "image": "ghcr.io/stefanprodan/podinfo:6.7.0", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "exec": { + "command": [ + "podcli", + "check", + "http", + "localhost:9898/healthz" + ] + }, + "failureThreshold": 3, + "initialDelaySeconds": 1, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 5 + }, + "name": "podinfo", + "ports": [ + { + "containerPort": 9898, + "name": "http", + "protocol": "TCP" + }, + { + "containerPort": 9797, + "name": "http-metrics", + "protocol": "TCP" + }, + { + "containerPort": 9999, + "name": "grpc", + "protocol": "TCP" + } + ], + "readinessProbe": { + "exec": { + "command": [ + "podcli", + "check", + "http", + "localhost:9898/readyz" + ] + }, + "failureThreshold": 3, + "initialDelaySeconds": 1, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 5 + }, + "resources": { + "requests": { + "cpu": "1m", + "memory": "16Mi" + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/data", + "name": "data" + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30, + "volumes": [ + { + "emptyDir": {}, + "name": "data" + } + ] + } + } + }, + "status": { + "availableReplicas": 1, + "conditions": [ + { + "lastTransitionTime": "2024-07-11T17:36:53Z", + "lastUpdateTime": "2024-07-11T17:36:53Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + }, + { + "lastTransitionTime": "2024-07-11T17:36:53Z", + "lastUpdateTime": "2024-07-11T17:36:56Z", + "message": "ReplicaSet \"my-release-podinfo-fb6d4888f\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + } + ], + "observedGeneration": 1, + "readyReplicas": 1, + "replicas": 0, + "updatedReplicas": 1 + } + } +} \ No newline at end of file diff --git a/demo/develop-validation/resources.json b/demo/develop-validation/resources.json new file mode 100644 index 00000000..a90e6dbb --- /dev/null +++ b/demo/develop-validation/resources.json @@ -0,0 +1,177 @@ +{ + "podinfoDeployment": { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "1", + "meta.helm.sh/release-name": "my-release", + "meta.helm.sh/release-namespace": "podinfo" + }, + "creationTimestamp": "2024-07-11T17:36:53Z", + "generation": 1, + "labels": { + "app.kubernetes.io/managed-by": "Helm", + "app.kubernetes.io/name": "my-release-podinfo", + "app.kubernetes.io/version": "6.7.0", + "helm.sh/chart": "podinfo-6.7.0" + }, + "name": "my-release-podinfo", + "namespace": "podinfo", + "resourceVersion": "1445826", + "uid": "443f38fa-9f7a-4df0-9fe9-098561c0ba77" + }, + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "my-release-podinfo" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": "25%", + "maxUnavailable": 1 + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "annotations": { + "prometheus.io/port": "9898", + "prometheus.io/scrape": "true" + }, + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/name": "my-release-podinfo" + } + }, + "spec": { + "containers": [ + { + "command": [ + "./podinfo", + "--port=9898", + "--cert-path=/data/cert", + "--port-metrics=9797", + "--grpc-port=9999", + "--grpc-service-name=podinfo", + "--level=info", + "--random-delay=false", + "--random-error=false" + ], + "env": [ + { + "name": "PODINFO_UI_COLOR", + "value": "#34577c" + } + ], + "image": "ghcr.io/stefanprodan/podinfo:6.7.0", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "exec": { + "command": [ + "podcli", + "check", + "http", + "localhost:9898/healthz" + ] + }, + "failureThreshold": 3, + "initialDelaySeconds": 1, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 5 + }, + "name": "podinfo", + "ports": [ + { + "containerPort": 9898, + "name": "http", + "protocol": "TCP" + }, + { + "containerPort": 9797, + "name": "http-metrics", + "protocol": "TCP" + }, + { + "containerPort": 9999, + "name": "grpc", + "protocol": "TCP" + } + ], + "readinessProbe": { + "exec": { + "command": [ + "podcli", + "check", + "http", + "localhost:9898/readyz" + ] + }, + "failureThreshold": 3, + "initialDelaySeconds": 1, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 5 + }, + "resources": { + "requests": { + "cpu": "1m", + "memory": "16Mi" + } + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/data", + "name": "data" + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30, + "volumes": [ + { + "emptyDir": {}, + "name": "data" + } + ] + } + } + }, + "status": { + "availableReplicas": 1, + "conditions": [ + { + "lastTransitionTime": "2024-07-11T17:36:53Z", + "lastUpdateTime": "2024-07-11T17:36:53Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + }, + { + "lastTransitionTime": "2024-07-11T17:36:53Z", + "lastUpdateTime": "2024-07-11T17:36:56Z", + "message": "ReplicaSet \"my-release-podinfo-fb6d4888f\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + } + ], + "observedGeneration": 1, + "readyReplicas": 1, + "replicas": 1, + "updatedReplicas": 1 + } + } +} \ No newline at end of file diff --git a/demo/develop-validation/validation.yaml b/demo/develop-validation/validation.yaml new file mode 100644 index 00000000..2ea6473a --- /dev/null +++ b/demo/develop-validation/validation.yaml @@ -0,0 +1,42 @@ +metadata: + name: check-podinfo-health + uuid: ad38ef57-99f6-4ac6-862e-e0bc9f55eebe +domain: + type: kubernetes + kubernetes-spec: + resources: + - name: podinfoDeployment + resource-rule: + name: my-release-podinfo + namespaces: [podinfo] + group: apps + version: v1 + resource: deployments +provider: + type: opa + opa-spec: + rego: | + package validate + import rego.v1 + + # Default values + default validate := false + default msg := "Not evaluated" + + # Validation result + validate if { + check_podinfo_healthy.result + } + msg = check_podinfo_healthy.msg + + check_podinfo_healthy = {"result": true, "msg": msg} if { + input.podinfoDeployment.status.replicas > 0 + input.podinfoDeployment.status.availableReplicas == input.podinfoDeployment.status.replicas + msg := "Number of replicas > 0 and all replicas are available." + } else = {"result": false, "msg": msg} if { + msg := "Podinfo not available." + } + output: + validation: validate.validate + observations: + - validate.msg \ No newline at end of file diff --git a/demo/namespace.yaml b/demo/simple/namespace.yaml similarity index 100% rename from demo/namespace.yaml rename to demo/simple/namespace.yaml diff --git a/demo/oscal-component-kyverno.yaml b/demo/simple/oscal-component-kyverno.yaml similarity index 100% rename from demo/oscal-component-kyverno.yaml rename to demo/simple/oscal-component-kyverno.yaml diff --git a/demo/oscal-component-opa.yaml b/demo/simple/oscal-component-opa.yaml similarity index 100% rename from demo/oscal-component-opa.yaml rename to demo/simple/oscal-component-opa.yaml diff --git a/demo/pod.fail.yaml b/demo/simple/pod.fail.yaml similarity index 100% rename from demo/pod.fail.yaml rename to demo/simple/pod.fail.yaml diff --git a/demo/pod.pass.yaml b/demo/simple/pod.pass.yaml similarity index 100% rename from demo/pod.pass.yaml rename to demo/simple/pod.pass.yaml diff --git a/docs/cli-commands/assessments/README.md b/docs/cli-commands/assessments/README.md new file mode 100644 index 00000000..ef2a78d4 --- /dev/null +++ b/docs/cli-commands/assessments/README.md @@ -0,0 +1,10 @@ +# Assessment + +The Assessments suite of Lula commands are intended to facilitate the generation of `assessment-plans` and `assessment-results` from `component-defintion` models. The following diagram indicates the OSCAL model inputs, commands, and output OSCAL models expected from each of these commands. + +```mermaid +flowchart TD + A[Component Definition]-->|validate|C[Assessment Results] + A[Component Definition]-->|generate|B[Assessment Plan] + B[Assessment Plan]-->|assess|C[Assessment Results] +``` \ No newline at end of file diff --git a/docs/cli-commands/assessments/assess.md b/docs/cli-commands/assessments/assess.md new file mode 100644 index 00000000..b370db47 --- /dev/null +++ b/docs/cli-commands/assessments/assess.md @@ -0,0 +1,8 @@ +# Assess - PLANNED + +This command performs the `assessment` between a `component-definition` for a system-level component, outputing the `assessment-plan` OSCAL artifact. + +Use the command as follows: +```shell +lula assess -f oscal-component.yaml -o assessment-plan.yaml +``` diff --git a/docs/cli-commands/evaluate.md b/docs/cli-commands/assessments/evaluate.md similarity index 100% rename from docs/cli-commands/evaluate.md rename to docs/cli-commands/assessments/evaluate.md diff --git a/docs/cli-commands/assessments/validate.md b/docs/cli-commands/assessments/validate.md new file mode 100644 index 00000000..93a7d74c --- /dev/null +++ b/docs/cli-commands/assessments/validate.md @@ -0,0 +1,22 @@ +# Validate + +The `validate` command aims to bridge the gap between fully developing the `assessment-plan` and allowing for testing of the existing `component-definition`. + +## Usage + +```bash +lula validate -f /path/to/oscal-component.yaml +``` + +## Options + +- `-f, --input-file`: The path to the OSCAL component definition file. +- `-o, --output-file`: [Optional] The path to the output assessment results file. Creates a new file or appends to existing. If not specified, the output file is `./assessment-results.yaml` +- `--confirm-execution`: [Optional] Flag to skip execution confirmation prompt. Only relevant when running a validation with a domain that performs some execution. +- `--non-interactive`: [Optional] Flag to indicate running non-interactively, i.e., does not request user to confirm validations with execution. + +## Examples + +This command is used both locally as an evaluation of the Component Definition to understand the component's compliance. It's also implemented in CI workflows to continually evaluate the evolution of a system during development. See the following relevant tutorials: + +- ... \ No newline at end of file diff --git a/docs/cli-commands/dev/README.md b/docs/cli-commands/dev/README.md new file mode 100644 index 00000000..972aeddd --- /dev/null +++ b/docs/cli-commands/dev/README.md @@ -0,0 +1,3 @@ +# Dev + +Several `dev` commands exist in Lula to facilitate developer methods, such as generating and testing `Lula Validations`. \ No newline at end of file diff --git a/docs/cli-commands/dev/get_resources.md b/docs/cli-commands/dev/get_resources.md new file mode 100644 index 00000000..fd4d43bc --- /dev/null +++ b/docs/cli-commands/dev/get_resources.md @@ -0,0 +1,44 @@ +# Get Resources + +The `get-resources` command is used to execute the domain portion of the input Lula Validation manifest to extract the `resources` json. This helps while developing validations to ensure the domain specification is returning the expected resources. + +## Usage + +```bash +lula dev get-resources -f /path/to/validation.yaml +``` + +## Options + +- `-f, --input-file`: The path to the target validation manifest. +- `-o, --output-file`: [Optional] The path to the output file. If not specified, the output will print to STDOUT +- `-t, --timeout`: [Optional] Timeout when waiting for results from STDIN. +- `--confirm-execution`: [Optional] Flag to skip execution confirmation prompt. Only relevant when running a domain that performs some execution. + +## Examples + +To get resources and write to file +```bash +lula dev get-resources -f /path/to/validation.yaml -o /path/to/resources.json +``` + +To run get resources and automatically confirm execution +```bash +lula dev get-resources -f /path/to/validation.yaml --confirm-execution +``` + +To run get resources from stdin +```bash +cat /path/to/validation.yaml | lula dev get-resources +``` + +To hang indefinitely for stdin +```bash +lula dev get-resources -t -1 +``` + +To hang for timeout of 5 seconds +```bash +lula dev get-resources -t 5 +``` + diff --git a/docs/cli-commands/dev/validate.md b/docs/cli-commands/dev/validate.md new file mode 100644 index 00000000..d01f92b6 --- /dev/null +++ b/docs/cli-commands/dev/validate.md @@ -0,0 +1,46 @@ +# Validate + +The `validate` command is used to run a single validation for the input Lula Validation manifest. This helps while developing validations to exercise the policy engines to ensure correctness and expose the outputs that will appear in the `assessment-results`. + +## Usage + +```bash +lula dev validate -f /path/to/validation.yaml +``` + +## Options + +- `-f, --input-file`: The path to the target validation manifest. +- `-o, --output-file`: [Optional] The path to the output file. If not specified, the output will print to STDOUT +- `-r, --resources-file`: [Optional] The path to the resources file; must be json. If not specified, resources will be read from the domain. +- `-e, --expected-result`: [Optional] The expected result of the validation, true or false. Default is true. +- `-t, --timeout`: [Optional] Timeout when waiting for results from STDIN. +- `--confirm-execution`: [Optional] Flag to skip execution confirmation prompt. Only relevant when running a domain that performs some execution. + +## Examples + +To run validation using a custom resources file +```bash +lula dev validate -f /path/to/validation.yaml -r /path/to/resources.json +``` + +To run validation and automatically confirm execution +```bash +lula dev validate -f /path/to/validation.yaml --confirm-execution +``` + +To run validation from stdin +```bash +cat /path/to/validation.yaml | lula dev validate +``` + +To hang indefinitely for stdin +```bash +lula dev validate -t -1 +``` + +To hang for timeout of 5 seconds +```bash +lula dev validate -t 5 +``` + diff --git a/docs/cli-commands/generation/README.md b/docs/cli-commands/generation/README.md new file mode 100644 index 00000000..0b274e77 --- /dev/null +++ b/docs/cli-commands/generation/README.md @@ -0,0 +1,13 @@ +# Generation + +Lula can generate OSCAL templates to help with authoring OSCAL artifacts. These generation processes help with initial build and maintenance of OSCAL artifacts and keeps with the vision of modular compliance artifacts that live with the source code. + +Lula currently generates the following OSCAL models: +* Component Definition + +Additional models that Lula is planning to generate via this method include: +* Profile +* System Security Plan, SSP +* Assessment Plan +* Plan of Actions and Milestones, POAM +* Catalog \ No newline at end of file diff --git a/docs/cli-commands/generate/component.md b/docs/cli-commands/generation/component.md similarity index 100% rename from docs/cli-commands/generate/component.md rename to docs/cli-commands/generation/component.md diff --git a/docs/cli-commands/tools/README.md b/docs/cli-commands/tools/README.md new file mode 100644 index 00000000..623f4e6a --- /dev/null +++ b/docs/cli-commands/tools/README.md @@ -0,0 +1,3 @@ +# Tools + +The `lula tools` or `lula t` commands facilitate usage of additional Lula features and go-oscal capabilities. \ No newline at end of file diff --git a/docs/cli-commands/tools/compose.md b/docs/cli-commands/tools/compose.md index 4637bd05..f81d0196 100644 --- a/docs/cli-commands/tools/compose.md +++ b/docs/cli-commands/tools/compose.md @@ -1,4 +1,4 @@ -# Compose Command +# Compose The `lula tools compose` command is used to compose an OSCAL component definition. It is used to compose remote validations within a component definition in order to resolve any references for portability. diff --git a/docs/cli-commands/tools/uuidgen.md b/docs/cli-commands/tools/uuidgen.md new file mode 100644 index 00000000..5c84cdc8 --- /dev/null +++ b/docs/cli-commands/tools/uuidgen.md @@ -0,0 +1,9 @@ +# UUID Gen + +The `uuidgen` command is a helper tool to generate a compliant UUID (universally unique identifier). These UUIDs are relevant across various parameters within the OSCAL models + +## Usage + +```bash +lula tools uuidgen +``` \ No newline at end of file diff --git a/docs/community-and-contribution/README.md b/docs/community-and-contribution/README.md index 8cc522fb..c1878dec 100644 --- a/docs/community-and-contribution/README.md +++ b/docs/community-and-contribution/README.md @@ -1 +1,17 @@ -# Community and Contribution \ No newline at end of file +# Community and Contribution + +## Community + +Welcome to the Lula Community, happy to have you here! Come chat with us in the [Lula Dev Kubernetes Slack Channel](https://kubernetes.slack.com/archives/C07462AMMRB). Whether you're new to Lula or familiar with the concepts, we encourage you to ask questions, share your ideas, and collaborate with us to keep build something amazing together! + +## Contributing + +Thank you for your interest in contributing! + +Issues in the backlog labeled `good first issue` is a great place to start for newcomers. If you have any questions about the process or the issue you are working on feel free to reach out in [slack](https://kubernetes.slack.com/archives/C07462AMMRB). + +Please see our [Contributor's Guide](https://github.com/defenseunicorns/lula/blob/main/CONTRIBUTING.md) for additional information. + +### Development Requirements + +- Go ~>1.22.x diff --git a/docs/community-and-contribution/triage-label.md b/docs/community-and-contribution/triage-label.md index 1b27af45..b55468a0 100644 --- a/docs/community-and-contribution/triage-label.md +++ b/docs/community-and-contribution/triage-label.md @@ -1,3 +1,5 @@ +# Triage + ## Using the Triage Label for Effective Issue Tracking ### Overview diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md new file mode 100644 index 00000000..009bb49a --- /dev/null +++ b/docs/getting-started/README.md @@ -0,0 +1,66 @@ +# Getting Started + +## Installation + +Currently you can install Lula a couple different ways. A brew formulae is in the plan, but not currently implemented. Lula is currently only compatible with Linux/MacOS distributions. + +### From Source + +1) Clone the repository to your local machine and change into the `lula` directory + ```shell + git clone https://github.com/defenseunicorns/lula.git && cd lula + ``` + +2) While in the `lula` directory, compile the tool into an executable binary. This outputs the `lula` binary to the `bin` directory. + ```shell + make build + ``` + +3) On most Linux distributions, install the binary onto your $PATH by moving the downloaded binary to the /usr/local/bin directory: + ```shell + sudo mv ./bin/lula /usr/local/bin/lula + ``` + +### Download + +1) Navigate to the Latest Release Page: +Open your web browser and go to the following URL to access the latest release of Lula: +https://github.com/defenseunicorns/lula/releases/latest + +2) Download the Binary: +On the latest release page, find and download the appropriate binary for your operating system. E.g., `lula__Linux_amd64` + +3) Download the checksums.txt: +In the list of assets on the release page, locate and download the checksums.txt file. This file contains the checksums for all the binaries in the release. + +4) Verify the Download: +After downloading the binary and checksums.txt, you should verify the integrity of the binary using the checksum provided: + * Open a terminal and navigate to the directory where you downloaded the binary and checksums.txt. + * Run the following command to verify the checksum if using Linux: + ```shell + sha256sum -c checksums.txt --ignore-missing + ``` + * Run the following command to verify the checksum if using MacOS: + ```shell + shasum -a 256 -c checksums.txt --ignore-missing + ``` + +5) On most Linux distributions, install the binary onto your $PATH by moving the downloaded binary to the /usr/local/bin directory: + ```shell + sudo mv ./download/path/lula__Linux_amd64 /usr/local/bin/lula + ``` + +## Quick Start + +See the following tutorials for some introductory lessons on how to use Lula. If you are unfamiliar with Lula, the best place to start is the "Simple Demo". + +### Tutorials + +* [Simple Demo](./simple-demo.md) +* [Using Lula in CI](./lula-in-ci.md) + +### Lula Validations + +Lula Validation manifests are the underlying mechanisms that dictates the evaluation of a system against a control as resulting in `satisfied` or `not satisfied`. A Lula Validation is linked to a control within a component definition via the OSCAL-specific property, [links](../oscal/oscal-validation-links.md). + +Developing Lula Validations can sometimes be more art than science, but generally they should aim to be clear, concise, and robust to system changes. See our guide for [developing Lula Validations](./develop-a-validation.md) and the [references](../reference/README.md) for additional information. \ No newline at end of file diff --git a/docs/getting-started/develop-a-validation.md b/docs/getting-started/develop-a-validation.md new file mode 100644 index 00000000..ebb881e0 --- /dev/null +++ b/docs/getting-started/develop-a-validation.md @@ -0,0 +1,273 @@ +# Develop a Validation + +This document describes some best practices for developing and using a [Lula Validation](), the primary mechanism for evaluation of system's compliance against a specified control. + +## About + +Lula Validations are constructed by establishing the collection of measurements about a system, given by the specified **Domain**, and the evaluation of adherence, performed by the **Provider**. + +The currently supported domains are: +- API +- Kubernetes + +The currently supported providers are: +- OPA (Open Policy Agent) +- Kyverno + +## Creating a Sample Validation + +Here, we will step through creating a sample validation using the Kubernetes domain and OPA provider. Generating a validation is in the scope of answering some control or standard. For instance, our control might be something like "system implements test application as target for development purposes". Our validation should then seek to _prove_ that some "test application" is running in our domain, i.e., Kubernetes. + +### Pre-Requistes + +* Lula installed +* Kubectl +* Helm +* A running Kubernetes cluster + - Kind + - `kind create cluster -n lula-test` + - K3d + - `k3d cluster create lula-test` +* Podinfo deployed in the cluster + ```sh + $ helm repo add podinfo https://stefanprodan.github.io/podinfo + + $ helm upgrade -i my-release podinfo/podinfo -n podinfo --create-namespace + ``` + +### Steps + +>[!NOTE] +> Demo files can be found in the lula repository under `demo/develop-validation` + +1. Assume we have some component definition for Podinfo with the associated standard we are trying to prove the system satisfies: + ```yaml + component-definition: + uuid: a506014d-cb8a-4db9-ac48-ef72f7209a60 + metadata: + last-modified: 2024-07-11T13:38:09.633174-04:00 + oscal-version: 1.1.2 + published: 2024-07-11T13:38:09.633174-04:00 + remarks: Lula Generated Component Definition + title: Component Title + version: 0.0.1 + components: + - uuid: 75859c1e-30f5-4fde-9ad4-c79f863b049f + type: software + title: podinfo + description: Sample application + control-implementations: + - uuid: a3039927-839c-5745-ac4e-a9993bcd60ed + source: https://github.com/defenseunicorns/lula + description: Control Implementation Description + implemented-requirements: + - uuid: 257d2b2a-fda7-49c5-9a2b-acdc995bc8e5 + control-id: ID-1 + description: >- + Podinfo, a sample application, is deployed into the cluster and exposed for testing purposes. + remarks: >- + System implements test application as target for development purposes. + ``` + +2. We recognize that we can satisfy this control by proving that podinfo is alive in the cluster. If we know nothing about podinfo, we may first want to identify which Kubernetes constructs are used in it's configuration: + ```sh + $ kubectl get all -n podinfo + + NAME READY STATUS RESTARTS AGE + pod/my-release-podinfo-fb6d4888f-ptlss 1/1 Running 0 17m + + NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE + service/my-release-podinfo ClusterIP 10.43.172.65 9898/TCP,9999/TCP 17m + + NAME READY UP-TO-DATE AVAILABLE AGE + deployment.apps/my-release-podinfo 1/1 1 1 17m + + NAME DESIRED CURRENT READY AGE + replicaset.apps/my-release-podinfo-fb6d4888f 1 1 1 17m + ``` + +3. Now that we know what resources are in the `podinfo` namespace, we can use our kubernetes knowledge to deduce that proving podinfo is healthy in the cluster could be performed by looking at the `status` of the podinfo deployment for the `replicas` value to match `readyReplicas`: + ```sh + $ kubectl get deployment my-release-podinfo -n podinfo -o json | jq '.status' + + { + "availableReplicas": 1, + "conditions": [ + { + "lastTransitionTime": "2024-07-11T17:36:53Z", + "lastUpdateTime": "2024-07-11T17:36:53Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + }, + { + "lastTransitionTime": "2024-07-11T17:36:53Z", + "lastUpdateTime": "2024-07-11T17:36:56Z", + "message": "ReplicaSet \"my-release-podinfo-fb6d4888f\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + } + ], + "observedGeneration": 1, + "readyReplicas": 1, + "replicas": 1, + "updatedReplicas": 1 + } + ``` + +4. With this we should now have enough information to write our Lula Validation! First construct the top-matter `metadata`: + + Run `lula tools uuidgen` to get a unique ID for your validation + ```bash + $ lula tools uuidgen + ad38ef57-99f6-4ac6-862e-e0bc9f55eebe + ``` + + Add a `validation.yaml` file with the following + ```yaml + metadata: + name: check-podinfo-health + uuid: ad38ef57-99f6-4ac6-862e-e0bc9f55eebe + ``` + +5. Construct the `domain`: + + Since we are extracting Kubernetes manifest data as validation "proof", the domain we use should be `kubernetes`. + ```yaml + domain: + type: kubernetes + kubernetes-spec: + resources: + - name: podinfoDeployment + resource-rule: + name: my-release-podinfo + namespaces: [podinfo] + group: apps + version: v1 + resource: deployments + ``` + + Note a few things about the specification for obtaining these kubernetes resources: + - `resources` key is used as an array of resources we are asking for from the cluster + - `name` is the keyword that will be used as an input to the policy, stated below in the provider. Note - to play nicely with the policy, it is best to make this a single word, camel-cased if desired. + - `resource-rule` is the api specification for the resource being extracted + - `name` is the name of our deployment, `my-release-podinfo` + - `namespaces` is the list of namespaces, one can be provided but must be in list format + - `group`, `version`, `resource` is the compliant values to access the [kubernetes API](https://pkg.go.dev/k8s.io/apimachinery@v0.30.2/pkg/runtime/schema) + + See [reference](../reference/README.md) for more information about the Lula Validation schema and kubernetes domain. + +6. Construct the `provider` and write the OPA policy: + + Any provider should be compatible with the domain outputs, here we've decided to use OPA and rego, so our `provider` section is as follows: + ```yaml + provider: + type: opa + opa-spec: + rego: | + package validate + import rego.v1 + + # Default values + default validate := false + default msg := "Not evaluated" + + # Validation result + validate if { + check_podinfo_healthy.result + } + msg = check_podinfo_healthy.msg + + check_podinfo_healthy = {"result": true, "msg": msg} if { + input.podinfoDeployment.status.replicas > 0 + input.podinfoDeployment.status.availableReplicas == input.podinfoDeployment.status.replicas + msg := "Number of replicas > 0 and all replicas are available." + } else = {"result": false, "msg": msg} { + msg := "Podinfo not available." + } + output: + validation: validate.validate + observations: + - validate.msg + ``` + + The Rego policy language can be a little funny looking at first glance, check out both the rego docs and the [OPA Provider](../reference/providers/opa-provider.md) reference for more information about rego. + + With that said, some things are important to highlight about the policy + - `package validate` is mandatory at the top (you can use any package name you want, but if a different value is used the `output.validation` needs to be updated accordingly) + - `import rego.v1` is optional, but recommended as OPA looks to [upgrade to v1](https://www.openpolicyagent.org/docs/latest/opa-1/) + - The "Default values" section is best practice to set these to protect against a result that yields undefined values for these variables + - The "Validation result" section defines the rego evaluation on the `input.podinfoDeployment` - checking that both the number of replicas is greater than 0 and the available and requested replicas are equal. + +7. Putting it all together we are left with the `validation.yaml`, let's run some commands to validate our validation: + + Get the resources to visually inspect that they are what you expect from the `domain` and in the right struction for the provider's policy: + ```sh + $ lula dev get-resources -f validation.yaml -o resources.json + ``` + + The result should be a `resources.json` file that looks roughly as follows: + ```json + { + "podinfoDeployment": { + "apiVersion": "apps/v1", + "kind": "Deployment", + # ... rest of the json + } + } + ``` + + Now check the validation is resulting in the expected outcome: + ```sh + $ lula dev validate -f validation.yaml + • Observations: + • --> validate.msg: Number of replicas > 0 and all replicas are available. + • Validation completed with 1 passing and 0 failing results + ``` + + If we expected this validation to fail, we would have added `-e=false` + +8. Now that we have our baseline validation, and we know it is returning an expected result for our current cluster configuration, we should probably ensure that the policy results are successful when other resource cases exist. There are really two options here: + + 1) Manually modify the resources in your cluster and re-run `lula dev validate` + + 2) Manually modify the `resources.json` and test those + + If we have a test cluster, perhaps changing some things about it is acceptable, but for this case I'm just going to take the path of least resistance and modify the `resources.json`: + + Copy your `resources.json` and rename to `resources-bad.json`. First, find `podinfoDeployment.status.replicas` and change the value to 0. Run `lula dev validate` with those resources as the input, along with our expected failure outcome: + + ```sh + $ lula dev validate -f validation.yaml -r resources-bad.json -e=false + • Observations: + • --> validate.msg: Podinfo not available. + • Validation completed with 0 passing and 1 failing results + ``` + + Success! Additional conditions can be tested this way to fully stress-test the validity of the policy. + +9. Finally, we can bring this back into the `component-definition`. This validation should be added as a link to the respective `implemented-requirement`: + ```yaml + # ... rest of component definition + implemented-requirements: + - uuid: 257d2b2a-fda7-49c5-9a2b-acdc995bc8e5 + control-id: ID-1 + description: >- + Podinfo, a sample application, is deployed into the cluster and exposed for testing purposes. + remarks: >- + System implements test application as target for development purposes. + links: + - href: 'file:./validation.yaml + ref: lula + text: Check that Podinfo is healthy + ``` + +10. Now that we have our full OSCAL Component Definition model specified, we can take this off to `validate` and `evaluate` the system! + +## Limitations + +We are aware that many of these validations are brittle to environment changes, for instance if namespaces change. This is a known limitation and on our roadmap as something to offer a possible templating solution for in the future. + +Additionally, since we are adding these validations to OSCAL yaml documents, there is some ugliness with having to compose strings of yaml into yaml. We support "remote" validations, where instead of a reference to a backmatter uuid, instead a link to a file is provided. A limitation of that currently is that it does not support authentication if the remote link is in a protected location. \ No newline at end of file diff --git a/docs/getting-started/lula-in-ci.md b/docs/getting-started/lula-in-ci.md new file mode 100644 index 00000000..9c0c5433 --- /dev/null +++ b/docs/getting-started/lula-in-ci.md @@ -0,0 +1,102 @@ +# Lula in CI + +Lula is designed to evaluate the _continual_ compliance of a system, and as such is a valuable tool to implement in a CI environment to provide rapid feedback to developers if a system moves out of compliance. The [Lula-Action Repo](https://github.com/defenseunicorns/lula-action) supports the use of Lula in github workflows and this document provides an outline for implementation. + +### Pre-Requisite + +To use Lula to `validate` and `evaluate` a system _in development_, a pre-requisite is having an OSCAL Component Definition model, along with linked `Lula Validations` existing in the repository, a sample structure follows: + +```bash +. +|-- .github +| |-- workflows +|-- |-- |-- lint.yaml # Existing workflow to lint +|-- |-- |-- test.yaml # Existing workflow to test system +|-- README.md +|-- LICENSE +|-- compliance +|-- |-- oscal-component.yaml # OSCAL Component Definition +|-- src +| |-- main +| |-- test +``` + +### Steps + +1) Add Lula linting to `.github/workflows/lint.yaml`: + + ```yaml + name: Lint + + on: + pull_request: + branches: [ "main" ] + + jobs: + lint: + runs-on: ubuntu-latest + + # ... Other jobs + + - name: Setup Lula + uses: defenseunicorns/lula-action/setup@main + with: + version: v0.4.1 + + - name: Lint OSCAL file + uses: defenseunicorns/lula-action/lint@main + with: + oscal-target: ./compliance/oscal-component.yaml + + # ... Other jobs + ``` + + Additional linting targets may be added to this list as comma separated values, e.g., `component1.yaml,component2.yaml`. Note that linting is only validating the correctness of the OSCAL. + +2) Add Lula validation and evaluation to the testing workflow, `.github/workflows/test.yaml`: + + ```yaml + name: Test + + on: + pull_request: + branches: [ "main" ] + + jobs: + test: + runs-on: ubuntu-latest + + # ... Other jobs + + - name: Setup Lula + uses: defenseunicorns/lula-action/setup@main + with: + version: v0.4.1 + + - name: Validate Component Definition + uses: defenseunicorns/lula-action/validate@main + with: + oscal-target: ./compliance/oscal-component.yaml + threshold: ./assessment-results.yaml + + # ... Other jobs + test-upgrade: + runs-on: ubuntu-latest + + # ... Jobs to deploy previous system version + + - name: Setup Lula + uses: defenseunicorns/lula-action/setup@main + with: + version: v0.4.1 + + - name: Validate Component Definition + uses: defenseunicorns/lula-action/validate@main + with: + oscal-target: ./compliance/oscal-component.yaml + threshold: ./assessment-results.yaml + + # ... Jobs to upgrade system to current version + ``` + + The first `validate` under `test` outputs an `assessment-results` model that provide the assessment of the system in the current state. The second `validate` that occurs in the `test-upgrade` job runs a validation on the previous version of the system prior to upgrade. It then compares the old and new assessment results to either pass or fail the job - failure occurs when the current system's compliance is **worse** than the old system. \ No newline at end of file diff --git a/docs/getting-started/simple-demo.md b/docs/getting-started/simple-demo.md new file mode 100644 index 00000000..580ca35c --- /dev/null +++ b/docs/getting-started/simple-demo.md @@ -0,0 +1,193 @@ +# Simple Demo + +The following simple demo will step through a process to validate and evaluate Kubernets cluster resources for a simple `component-definition`. The following pre-requisites are required to successfully run this demo: + +### Pre-Requisites + +* Lula installed +* Kubectl +* A running Kubernetes cluster + - Kind + - `kind create cluster -n lula-test` + - K3d + - `k3d cluster create lula-test` + +### Steps + +1. Clone the Lula repository to your local machine and change into the `lula/demo/simple` directory + + ```shell + git clone https://github.com/defenseunicorns/lula.git && cd lula/demo/simple + ``` + +2. Apply the `namespace.yaml` file to create a namespace for the demo + + ```shell + kubectl apply -f namespace.yaml + ``` + +3. Apply the `pod.fail.yaml` to create a pod in your cluster + + ```shell + kubectl apply -f pod.fail.yaml + ``` + +4. The `oscal-component-opa.yaml` is a simple OSCAL Component Definition model which establishes a sample control <> validation mapping. The validation that provides a `satisfied` or `not-satisfied` result to the control simply checks if the required label value is set for `pod.fail`. Run the following command to `validate` the component given by the failing pod: + + ```shell + lula validate -f oscal-component-opa.yaml + ``` + + The output in your terminal should inform you that the single control validated is `not-satisfied`: + + ```shell + NOTE Saving log file to + /var/folders/6t/7mh42zsx6yv_3qzw2sfyh5f80000gn/T/lula-2024-07-08-10-22-57-840485568.log + • changing cwd to . + + 🔍 Collecting Requirements and Validations + • Found 1 Implemented Requirements + • Found 1 runnable Lula Validations + + 📐 Running Validations + ✔ Running validation a7377430-2328-4dc4-a9e2-b3f31dc1dff9 -> evaluated -> not-satisfied + + 💡 Findings + • UUID: c80f76c5-4c86-4773-91a3-ece127f3d55a + • Status: not-satisfied + • OSCAL artifact written to: assessment-results.yaml + ``` + + This will also produce an `assessment-results` model - review the findings and observations: + + ```yaml + assessment-results: + results: + - description: Assessment results for performing Validations with Lula version v0.4.1-1-gc270673 + findings: + - description: This control validates that the demo-pod pod in the validation-test namespace contains the required pod label foo=bar in order to establish compliance. + related-observations: + - observation-uuid: f03ffcd9-c18d-40bf-85f5-d0b1a8195ddb + target: + status: + state: not-satisfied + target-id: ID-1 + type: objective-id + title: 'Validation Result - Component:A9D5204C-7E5B-4C43-BD49-34DF759B9F04 / Control Implementation: A584FEDC-8CEA-4B0C-9F07-85C2C4AE751A / Control: ID-1' + uuid: c80f76c5-4c86-4773-91a3-ece127f3d55a + observations: + - collected: 2024-07-08T10:22:57.219213-04:00 + description: | + [TEST]: a7377430-2328-4dc4-a9e2-b3f31dc1dff9 - lula-validation + methods: + - TEST + relevant-evidence: + - description: | + Result: not-satisfied + uuid: f03ffcd9-c18d-40bf-85f5-d0b1a8195ddb + props: + - name: threshold + ns: https://docs.lula.dev/ns + value: "false" + reviewed-controls: + control-selections: + - description: Controls Assessed by Lula + include-controls: + - control-id: ID-1 + description: Controls validated + remarks: Validation performed may indicate full or partial satisfaction + start: 2024-07-08T10:22:57.219371-04:00 + title: Lula Validation Result + uuid: f9ae56df-8709-49be-a230-2d3962bbd5f9 + uuid: 5bf89b23-6172-47c9-9d1c-d308fa543d61 + ``` + +5. Now, apply the `pod.pass.yaml` file to your cluster to configure the pod to pass compliance validation: + + ```shell + kubectl apply -f pod.pass.yaml + ``` + +6. Run the following command in the `lula` directory: + + ```shell + lula validate -f oscal-component-opa.yaml + ``` + + The output should now show the pod as passing the compliance requirement: + + ```shell + NOTE Saving log file to + /var/folders/6t/7mh42zsx6yv_3qzw2sfyh5f80000gn/T/lula-2024-07-08-10-25-47-3097295143.log + • changing cwd to . + + 🔍 Collecting Requirements and Validations + • Found 1 Implemented Requirements + • Found 1 runnable Lula Validations + + 📐 Running Validations + ✔ Running validation a7377430-2328-4dc4-a9e2-b3f31dc1dff9 -> evaluated -> satisfied + + 💡 Findings + • UUID: 5a991d1f-745e-4acb-9435-373174816fcc + • Status: satisfied + • OSCAL artifact written to: assessment-results.yaml + ``` + + This will append to the assessment-results file with a new result: + + ```yaml + - description: Assessment results for performing Validations with Lula version v0.4.1-1-gc270673 + findings: + - description: This control validates that the demo-pod pod in the validation-test namespace contains the required pod label foo=bar in order to establish compliance. + related-observations: + - observation-uuid: a1d55b82-c63f-47da-8fab-87ae801357ac + target: + status: + state: satisfied + target-id: ID-1 + type: objective-id + title: 'Validation Result - Component:A9D5204C-7E5B-4C43-BD49-34DF759B9F04 / Control Implementation: A584FEDC-8CEA-4B0C-9F07-85C2C4AE751A / Control: ID-1' + uuid: 5a991d1f-745e-4acb-9435-373174816fcc + observations: + - collected: 2024-07-08T10:25:47.633634-04:00 + description: | + [TEST]: a7377430-2328-4dc4-a9e2-b3f31dc1dff9 - lula-validation + methods: + - TEST + relevant-evidence: + - description: | + Result: satisfied + uuid: a1d55b82-c63f-47da-8fab-87ae801357ac + props: + - name: threshold + ns: https://docs.lula.dev/ns + value: "false" + reviewed-controls: + control-selections: + - description: Controls Assessed by Lula + include-controls: + - control-id: ID-1 + description: Controls validated + remarks: Validation performed may indicate full or partial satisfaction + start: 2024-07-08T10:25:47.6341-04:00 + title: Lula Validation Result + uuid: a9736e32-700d-472f-96a3-4dacf36fa9ce + ``` + +7. Now that two assessment-results are established, the `threshold` can be evaluated. Perform an `evaluate` to compare the old and new state of the cluster: + ```shell + lula evaluate -f oscal-component-opa.yaml + ``` + + The output will show that now the new threshold for the system assessment is the more _compliant_ evaluation of the control - i.e., the `satisfied` value of the Control ID-1 is the threshold. + ```shell + NOTE Saving log file to + /var/folders/6t/7mh42zsx6yv_3qzw2sfyh5f80000gn/T/lula-2024-07-08-10-29-53-4238890270.log + • New passing finding Target-Ids: + • ID-1 + • New threshold identified - threshold will be updated to result a9736e32-700d-472f-96a3-4dacf36fa9ce + • Evaluation Passed Successfully + ✔ Evaluating Assessment Results f9ae56df-8709-49be-a230-2d3962bbd5f9 against a9736e32-700d-472f-96a3-4dacf36fa9ce + • OSCAL artifact written to: ./assessment-results.yaml + ``` \ No newline at end of file diff --git a/docs/oscal/README.md b/docs/oscal/README.md index bac7556d..c67aa9c3 100644 --- a/docs/oscal/README.md +++ b/docs/oscal/README.md @@ -1 +1,5 @@ -# Open Security Controls Assessment Language \ No newline at end of file +# OSCAL + +OSCAL, Open Security Controls Assessment Language, is a NIST-led, machine-readable representation of various control models. Find out more about OSCAL [here](https://pages.nist.gov/OSCAL/about/). + +The sub-pages describe the interaction between Lula and OSCAL, for detail on the specific OSCAL models see the OSCAL documentation. \ No newline at end of file diff --git a/docs/oscal/models/assessment-results.md b/docs/oscal/assessment-results.md similarity index 100% rename from docs/oscal/models/assessment-results.md rename to docs/oscal/assessment-results.md diff --git a/docs/reference/domains/README.md b/docs/reference/domains/README.md index 01c80935..a092405e 100644 --- a/docs/reference/domains/README.md +++ b/docs/reference/domains/README.md @@ -1 +1,18 @@ -# Domains \ No newline at end of file +# Domains + +Domains, generically, are the areas or categories from which data is collected to be evaluated by a `Lula Validation`. Currently supported Domains are: + +* Kubernetes +* API + +The domain block of a `Lula Validation` is given as follows, where the sample is indicating a Kubernetes domain is in use: +```yaml +# ... Rest of Lula Validation +domain: + type: kubernetes # kubernetes or api accepted + kubernetes-spec: + # ... Rest of kubernetes-spec +# ... Rest of Lula Validation +``` + +Each domain has a particular specification, given by the respective `-spec` field of the `domain` property of the `Lula Validation`. The sub-pages describe each of these specifications in greater detail. \ No newline at end of file diff --git a/docs/reference/domains/api-domain.md b/docs/reference/domains/api-domain.md new file mode 100644 index 00000000..e37c39b5 --- /dev/null +++ b/docs/reference/domains/api-domain.md @@ -0,0 +1,3 @@ +# API Domain + +The API Domain allows for collection of data generically from API endpoints. \ No newline at end of file diff --git a/docs/reference/domains/kubernetes-domain.md b/docs/reference/domains/kubernetes-domain.md index 97eaaa43..067676e4 100644 --- a/docs/reference/domains/kubernetes-domain.md +++ b/docs/reference/domains/kubernetes-domain.md @@ -1,21 +1,23 @@ # Kubernetes Domain -The Kubernetes domain provides Lula with a common interface for data collection of Kubernetes artifacts for use across many Lula Providers. +The Kubernetes domain provides Lula access to data collection of Kubernetes resources. >[!Important] >This domain supports both read and write operations on the Kubernetes cluster in the current context, so use with care ## Specification -Resources are read from the Kubernetes cluster when using the Kubernetes domain specification as follows: +The Kubernetes specification can be used to return Kubernetes resources as JSON data to the providers. The specification allows for both manifest and resource field data to be retreived. + +The following sample `kubernetes-spec` yields a list of all pods in the `validation-test` namespace. ```yaml domain: type: kubernetes kubernetes-spec: - resources: - - name: podsvt # Required - Identifier to be read by the policy - resource-rule: # Required - resource selection criteria, at least one resource rule is required + resources: # Optional - Group of resources to read from Kubernetes + - name: podsvt # Required - Identifier to the list or set read by the policy + resource-rule: # Required - Resource selection criteria, at least one resource rule is required name: # Optional - Used to retrieve a specific resource in a single namespace group: # Optional - empty or "" for core group version: v1 # Required - Version of resource @@ -25,25 +27,27 @@ domain: jsonpath: # Required - Jsonpath specifier of where to find the field from the top level object type: # Optional - Accepts "json" or "yaml". Default is "json". base64: # Optional - Boolean whether field is base64 encoded - ``` > [!Tip] > Lula supports eventual-consistency through use of an optional `wait` field in the `kubernetes-spec`. ```yaml -wait: - condition: Ready - kind: pod/test-pod-wait - namespace: validation-test - timeout: 30s -resources: -- name: podsvt - resource-rule: - group: - version: v1 - resource: pods - namespaces: [validation-test] +domain: + type: kubernetes + kubernetes-spec: + wait: # Optional - Group of resources to read from Kubernetes + condition: Ready # ... + kind: pod/test-pod-wait # ... + namespace: validation-test # ... + timeout: 30s # ... + resources: + - name: podsvt + resource-rule: + group: + version: v1 + resource: pods + namespaces: [validation-test] ``` ### Resource Creation @@ -54,7 +58,7 @@ The Kubernetes domain also supports creating, reading, and destroying test resou domain: type: kubernetes kubernetes-spec: - create-resources: + create-resources: # Optional - Group of resources to be created/read/destroyed in Kubernetes - name: testPod # Required - Identifier to be read by the policy namespace: validation-test # Optional - Namespace to be created if applicable (no need to specify if ns exists OR resource is non-namespaced) manifest: | # Optional - Manifest string for resource(s) to create; Only optional if file is not specified @@ -129,7 +133,7 @@ provider: > Note how the rego now evaluates a single object called `podvt`. This is the name of the resource that is being validated. ## Extracting Resource Field Data -Many of the tool-specific configuration data is stored as json or yaml text inside configmaps and secrets. Some valuable data may also be stored in json or yaml strings in other resource locations, such as annotations. The "Field" parameter of the "ResourceRule" allows this data to be extracted and used by the Rego. +Many of the tool-specific configuration data is stored as json or yaml text inside configmaps and secrets. Some valuable data may also be stored in json or yaml strings in other resource locations, such as annotations. The `field` parameter of the `resource-rule` allows this data to be extracted and used by the Rego. Here's an example of extracting `config.yaml` from a test configmap: ```yaml @@ -142,7 +146,7 @@ domain: name: test-configmap group: version: v1 - resource: pods + resource: configmaps namespaces: [validation-test] field: jsonpath: .data.my-config.yaml @@ -165,3 +169,21 @@ configuration: anotherValue: subValue: ba ``` +This field also supports grabbing secret data using the optional `base64` field as follows +```yaml +domain: + type: kubernetes + kubernetes-spec: + resources: + - name: secretdata + resource-rule: + name: test-secret + group: + version: v1 + resource: secrets + namespaces: [validation-test] + field: + jsonpath: .data.secret-value.json + type: json + base64: true +``` diff --git a/docs/reference/providers/README.md b/docs/reference/providers/README.md index 561a124f..e6ceef3c 100644 --- a/docs/reference/providers/README.md +++ b/docs/reference/providers/README.md @@ -1 +1,18 @@ -# Providers \ No newline at end of file +# Providers + +Providers are the policy engines which evaluate the input data from the specified domain. Currently supported Providers are: + +* OPA (Open Policy Agent) +* Kyverno + +The provider block of a `Lula Validation` is given as follows, where the sample is indicating the OPA provider is in use: +```yaml +# ... Rest of Lula Validation +provider: + type: opa # opa or kyverno accepted + opa-spec: + # ... Rest of opa-spec +# ... Rest of Lula Validation +``` + +Each domain specification retreives a specific dataset, and each will return that data to the selected `Provider` in a domain-specific format. However, this data will always take the form of a JSON object when input to a `Provider`. For that reason, it is important that `Domain` and `Provider`specifications are not built wholly independently in a given Validation. \ No newline at end of file diff --git a/lula.svg b/lula.svg new file mode 100644 index 00000000..8ac1f5a6 --- /dev/null +++ b/lula.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cmd/dev/get-resources.go b/src/cmd/dev/get-resources.go index b13b9ed8..277b334e 100644 --- a/src/cmd/dev/get-resources.go +++ b/src/cmd/dev/get-resources.go @@ -15,13 +15,13 @@ var getResourcesOpts = &flags{} var getResourcesHelp = ` To get resources from lula validation manifest: - lula dev get-resources -f + lula dev get-resources -f /path/to/validation.yaml To get resources from lula validation manifest and write to file: - lula dev get-resources -f /path/to/manifest.json -o /path/to/output.json + lula dev get-resources -f /path/to/validation.yaml -o /path/to/output.json To get resources from lula validation and automatically confirm execution - lula dev get-resources -f /path/to/manifest.json --confirm-execution + lula dev get-resources -f /path/to/validation.yaml --confirm-execution To run validations using stdin: - cat | lula dev validate + cat /path/to/validation.yaml | lula dev get-resources To hang indefinitely for stdin: lula get-resources -t -1 To hang for timeout of 5 seconds: diff --git a/src/cmd/dev/validate.go b/src/cmd/dev/validate.go index ff76442f..40d9637e 100644 --- a/src/cmd/dev/validate.go +++ b/src/cmd/dev/validate.go @@ -16,14 +16,14 @@ import ( ) var validateHelp = ` -To run validations using a lula validation manifest: - lula dev validate -f -To run validations using a custom resources file: - lula dev validate -f -r -To run validations and automatically confirm execution - lula dev get-resources -f /path/to/manifest.json --confirm-execution -To run validations using stdin: - cat | lula dev validate +To run validation from a lula validation manifest: + lula dev validate -f /path/to/validation.yaml +To run validation using a custom resources file: + lula dev validate -f /path/to/validation.yaml -r /path/to/resources.json +To run validation and automatically confirm execution + lula dev validate -f /path/to/validation.yaml --confirm-execution +To run validation from stdin: + cat /path/to/validation.yaml | lula dev validate To hang indefinitely for stdin: lula dev validate -t -1 To hang for timeout of 5 seconds: