{"id":13788054,"url":"https://github.com/mxab/nacp","last_synced_at":"2025-05-12T02:31:00.267Z","repository":{"id":137103367,"uuid":"600415546","full_name":"mxab/nacp","owner":"mxab","description":"Admission Controller as a proxy for Nomad. Define OPA rules for validation and mutation or plugin remotes","archived":false,"fork":false,"pushed_at":"2025-05-07T21:54:25.000Z","size":519,"stargazers_count":43,"open_issues_count":4,"forks_count":3,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-05-07T22:33:06.314Z","etag":null,"topics":["admission-controller","devsecops","nomad","notary","notation","opa"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mxab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-11T12:28:44.000Z","updated_at":"2025-04-07T13:03:47.000Z","dependencies_parsed_at":"2023-03-29T12:52:02.790Z","dependency_job_id":"c4efdd80-74b2-4e82-886e-052ba48dc728","html_url":"https://github.com/mxab/nacp","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mxab%2Fnacp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mxab%2Fnacp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mxab%2Fnacp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mxab%2Fnacp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mxab","download_url":"https://codeload.github.com/mxab/nacp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253662570,"owners_count":21944097,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["admission-controller","devsecops","nomad","notary","notation","opa"],"created_at":"2024-08-03T21:00:35.464Z","updated_at":"2025-05-12T02:31:00.239Z","avatar_url":"https://github.com/mxab.png","language":"Go","funding_links":[],"categories":["Nomad","DevOps tools"],"sub_categories":["Blogs and Articles","User Interfaces and Dashboards"],"readme":"# NACP - Nomad Admission Control Proxy\n\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mxab_nacp\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=mxab_nacp)\n\nA proxy infront of the Nomad API that allows to perform mutation and validation on the job data.\n\n\n\n![nacp](https://user-images.githubusercontent.com/1607547/224442234-685950f7-43ff-4570-91d1-fe004827caef.png)\n\n## How\nIt intercepts the Nomad API calls that include job data (plan, register, validate) and performs mutation and validation on the job data. The job data is at that point is already transformed from HCL to JSON.\nIf any errors occur the proxy will return the error to the Nomad API caller.\nWarnings are attached to the Nomad response when they come back from the actual Nomad API.\n\nCurrently validation comes into two flavors:\n- Embedded OPA rules\n- Webhooks\n\n## Mutation\n\nDuring the mutation phase the job data is modified by the configured mutators.\n### OPA\nThe opa mutator uses the [OPA](https://www.openpolicyagent.org/) policy engine to perform the mutation.\nThe OPA rule is expects to return a [JSONPatch](https://jsonpatch.com/) object. The JSONPatch object is then applied to the job data.\nIt can also return errors and warnings.\nAn example rego could look like this:\n\n```rego\npackage hello_world_meta\nimport future.keywords\n\npatch contains ops if [\n\n   input.job.Name == \"greeting_job\"\n   ops:= {\n        \"op\": \"add\",\n        \"path\": \"/Meta\",\n        \"value\": {\n            \"hello\": \"world\"\n        }\n    }\n]\n\nerrors contains msg if {\n\n    input.job.Name == \"silent_job\"\n    msg := \"cannot greet\"\n}\n\nwarnings contains msg if {\n\n  input.job.Name == \"had_no_coffee_yet_job\"\n  msg := \"you should have coffee first\"\n}\n```\n\nFor the embedded you also have to define the query that is used to extract the patch from the OPA response:\n\n```hcl\nmutator \"opa_json_patch\" \"hello_world_opa_mutator\" {\n\n    opa_rule {\n        query = \u003c\u003cEOH\n        patch = data.hello_world_meta.patch\n        errors = data.hello_world_meta.errors\n        warnings = data.hello_world_meta.warnings\n        EOH\n        filename = \"hello_world_meta.rego\"\n    }\n}\n```\n\n### Webhook\n\nThe webhook mutator sends the job data to a configured endpoint and expects a JSONPatch object in return.\nIt can also return errors and warnings.\nThe JSONPatch object is then applied to the job data.\nAn example response could look like this:\n\n```json\n{\n  \"patch\": [\n    {\n      \"op\": \"add\",\n      \"path\": \"/Meta\",\n      \"value\": {\n        \"hello\": \"world\"\n      }\n    }\n  ],\n  \"errors\": [\n    \"some error\"\n  ],\n  \"warnings\": [\n    \"some warning\"\n  ]\n}\n```\n\nThe webhook mutator can be configured with the following options:\n\n```hcl\nmutator \"json_patch_webhook\" \"hello_world_webhook_mutator\" {\n\n  webhook {\n    endpoint = \"http://example.org/send/job/here\"\n    method = \"POST\"\n  }\n\n}\n```\n\nHint: You can also setup the OPA server as a webhook mutator. You can use the [system main package](https://www.openpolicyagent.org/docs/latest/rest-api/#execute-a-simple-query) to run the OPA server as a webhook mutator.\n\n## Validation\n\nDuring the validation phase the job data is validated by the configured validators. If any errors occur the proxy will return the error to the Nomad API caller.\nWarnings are attached to the Nomad response when they come back from the actual Nomad API.\n\n### OPA\n\nThe opa validator uses the [OPA](https://www.openpolicyagent.org/) policy engine to perform the validation.\nThe OPA rule is expects to return a list of errors and warnings.\nAn example rego could look like this:\n\n```rego\npackage costcenter_meta\n\nimport future.keywords.contains\nimport future.keywords.if\n\nerrors contains msg if {\n\n\tnot input.job.Meta.costcenter\n\tmsg := \"Every job must have a costcenter metadata label\"\n}\n\nerrors contains msg if {\n\tvalue := input.job.Meta.costcenter\n\n\tnot startswith(value, \"cccode-\")\n\tmsg := sprintf(\"Costcenter code must start with `cccode-`; found `%v`\", [value])\n}\n```\n\nThen configure the validator in the config file:\n\n```hcl\nvalidator \"opa\" \"costcenter_opa_validator\" {\n\n    opa_rule {\n        query = \u003c\u003cEOH\n        errors = data.costcenter_meta.errors\n        warnings = data.costcenter_meta.warnings\n        EOH\n        filename = \"costcenter_meta.rego\"\n    }\n}\n```\n\n### Webhook\n\nThe webhook validator sends the job data to a configured endpoint and expects a list of errors and warnings in return.\n\nThe response should include potential `errors` and `warnings`:\n\n```json\n{\n  \"errors\": [\n    \"some error\"\n  ],\n  \"warnings\": [\n    \"some warning\"\n  ]\n}\n```\n\nThe webhook validator can be configured with the following options:\n\n\n```hcl\nvalidator \"webhook\" \"some_webhook_validator\" {\n\n  webhook {\n    endpoint = \"http://example.org/send/job/here\"\n    method = \"POST\"\n  }\n\n}\n```\n\n## More Examples\n\nCheckout the [examples](./example) folder for more examples.\n\n## Usage\n### Run Proxy\n\n```bash\n$ nacp -config config.hcl\n```\n\nIt will launch per default on port 6464.\n\n### Send Job to Nomad via Proxy\n\n```bash\nNOMAD_ADDR=http://localhost:6464 nomad job run job.hcl\n```\n\n### Other Configuration\n\n### NACP Server\n\nThe NACP server can be configured with the following options:\n\n```hcl\nserver {\n  # The address the server will listen on\n  bind = \"0.0.0.0\"\n  port = 6464\n\n  tls { # If this is present nomad will use TLS\n    # The path to the certificate file\n    cert_file = \"cert.pem\"\n    # The path to the private key file\n    key_file = \"key.pem\"\n\n    # The path to the CA certificate file\n    ca_file = \"ca.pem\"\n  }\n}\n```\n\n### Nomad Upstream\n\nThe Nomad upstream can be configured with the following options:\n\n```hcl\nnomad {\n  # The address of the Nomad API\n  address = \"http://localhost:4646\"\n\n  tls { # If this is present nomad will use TLS\n    # The path to the certificate file\n    cert_file = \"cert.pem\"\n    # The path to the private key file\n    key_file = \"key.pem\"\n\n    # The path to the CA certificate file\n    ca_file = \"ca.pem\"\n  }\n}\n```\n\n### Notation\n\nImage signature validation can be done in two ways. Either by the `notation` validator or via the opa by using the `notation_verify_image` function which returns either `true` if the image is valid or `false` if the image is not valid.\nSee [example/notation](./example/notation) for an example.\n\nBoth validators expect a notation block. E.g.:\n\n```hcl\n...\nvalidator \"opa\" \"notation_opa_validator\" {\n\n  opa_rule {\n      ...\n  }\n  notation {\n    repo_plain_http   = false\n    trust_store_dir   = \"/some/path/to/truststore\"\n    trust_policy_file = \"/some/path/to/trustpolicy.json\"\n    credential_store_file = \"/some/path/to/credentialstore.json\"\n  }\n}\n```\n\nThe `credential_store_file` refers to the [oras' credential file] (https://docs.docker.com/engine/reference/commandline/cli/#docker-cli-configuration-file-configjson-properties)\n\ne.g.:\n```json\n{\n  \"auths\": {\n    \"https://my-registy.example.org\": {\n      \"auth\": \"\u003cbase64 encoded username:password\u003e\"\n    }\n  }\n}\n```\n\n\n# Note\nThis work was inspired by the internal [Nomad Admission Controller](https://github.com/hashicorp/nomad/blob/v1.5.0/nomad/job_endpoint_hooks.go#L74)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmxab%2Fnacp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmxab%2Fnacp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmxab%2Fnacp/lists"}