File coraza-spoa-0.4.0+git3.obscpio of Package coraza-spoa
07070100000000000081A400000000000000000000000168C6803E00000036000000000000000000000000000000000000002800000000coraza-spoa-0.4.0+git3/.pre-commit.hookecho "Executing precommit checks"
go run mage.go check07070100000001000081A400000000000000000000000168C6803E00002110000000000000000000000000000000000000002400000000coraza-spoa-0.4.0+git3/CHANGELOG.md# Changelog
## [0.4.0](https://github.com/corazawaf/coraza-spoa/compare/v0.3.0...v0.4.0) (2025-09-05)
### Features
* add support for ftw-tests ([dcabd18](https://github.com/corazawaf/coraza-spoa/commit/dcabd18b68987111d28e93df7af69a70e59990ef))
* print matched error logs as json if requested ([f4684ed](https://github.com/corazawaf/coraza-spoa/commit/f4684ededd43455fe0592fad5cb34814e1130854))
### Bug Fixes
* **deps:** update all non-major dependencies in .github/workflows/container-image.yaml ([#261](https://github.com/corazawaf/coraza-spoa/issues/261)) ([9b4a2bb](https://github.com/corazawaf/coraza-spoa/commit/9b4a2bb3a7f99f9c5167ef79d6fcb1d7545e50b6))
* **deps:** update all non-major dependencies in .github/workflows/test.yaml ([#268](https://github.com/corazawaf/coraza-spoa/issues/268)) ([1d83755](https://github.com/corazawaf/coraza-spoa/commit/1d83755b6b44c06ba9a68b9aa315043cf7d8a899))
* **deps:** update module github.com/corazawaf/coraza-coreruleset/v4 to v4.16.0 in go.mod ([#259](https://github.com/corazawaf/coraza-spoa/issues/259)) ([2f9819c](https://github.com/corazawaf/coraza-spoa/commit/2f9819c769d9b52afcb3becc78bfed79ce148f39))
* **deps:** update module github.com/prometheus/client_golang to v1.23.1 in go.mod ([3866c1b](https://github.com/corazawaf/coraza-spoa/commit/3866c1bcc1ca96e7013fc6d87b7ab395724cb0b0))
## [0.3.0](https://github.com/corazawaf/coraza-spoa/compare/v0.2.0...v0.3.0) (2025-07-02)
### Features
* add SetServerName to transaction ([c5252cc](https://github.com/corazawaf/coraza-spoa/commit/c5252cc794600345c028de58f3047baedf732300))
## 0.2.0 (2025-06-26)
### Features
* add -version flag for printing version and build info ([#214](https://github.com/corazawaf/coraza-spoa/issues/214)) ([153d4fb](https://github.com/corazawaf/coraza-spoa/commit/153d4fb4677ed0ea4bedfb15cc6469ab89cb17ec)), closes [#117](https://github.com/corazawaf/coraza-spoa/issues/117)
* replace request hook in example ([07483bc](https://github.com/corazawaf/coraza-spoa/commit/07483bc005c0c9b28aed8f0e0cdb2cd595339ef6)), closes [#111](https://github.com/corazawaf/coraza-spoa/issues/111)
* Reuse haproxy unique_id if present ([bfd8b24](https://github.com/corazawaf/coraza-spoa/commit/bfd8b2466ecd6f52e7193a26250710ed803fe1ca))
* support the users to configure the traffic fields they need to forward in HAProxy configuration file ([11c9415](https://github.com/corazawaf/coraza-spoa/commit/11c9415375c76d9edfd43d711f2f9cfc890abe5d))
### Bug Fixes
* 5 by validating null queries ([d828d74](https://github.com/corazawaf/coraza-spoa/commit/d828d74f60896568c5bbf71eb0de045b986e8182))
* **build:** set arch in magefile ([8482824](https://github.com/corazawaf/coraza-spoa/commit/8482824b360c5d29c0d85296b55dfb22322c7439))
* **ci:** minor corrections from code review ([e3e7b9d](https://github.com/corazawaf/coraza-spoa/commit/e3e7b9df73ab5af3b67fb9705a9a3226cc25df5a))
* **ci:** only use main branch for tags ([69403c6](https://github.com/corazawaf/coraza-spoa/commit/69403c6b8a1124d30f37375cc03df3fb812a8fd7))
* **ci:** run build on all branches ([e8f614b](https://github.com/corazawaf/coraza-spoa/commit/e8f614ba55f49dbf965e7f10c90edb54d37dc9dd))
* **ci:** set correct build output dir ([5dba63d](https://github.com/corazawaf/coraza-spoa/commit/5dba63d9522688884cdad71c4d5ac643a698742c))
* **ci:** use variable instead of fixed name ([e619181](https://github.com/corazawaf/coraza-spoa/commit/e619181264be4ea9cc83463b165c6b0aeea132ec))
* **config:** image build ([#100](https://github.com/corazawaf/coraza-spoa/issues/100)) ([b93d995](https://github.com/corazawaf/coraza-spoa/commit/b93d995fca765c8f27db651fb57dafed84eec34a))
* **deps:** update all non-major dependencies in go.mod ([#207](https://github.com/corazawaf/coraza-spoa/issues/207)) ([1dfb95f](https://github.com/corazawaf/coraza-spoa/commit/1dfb95fad3a7efc7f40a71bef5dd4b47a16ce869))
* **deps:** update all non-major dependencies to v2.16.1 in go.mod ([cfcabe5](https://github.com/corazawaf/coraza-spoa/commit/cfcabe5b78150d0e953adcab945714fe32ac0978))
* **deps:** update all non-major dependencies to v2.17.1 in go.mod ([#193](https://github.com/corazawaf/coraza-spoa/issues/193)) ([e7b0f46](https://github.com/corazawaf/coraza-spoa/commit/e7b0f46dbb28d154e3938b2c8e0f4e118a580bc7))
* **deps:** update all non-major dependencies to v2.18.0 in go.mod ([#199](https://github.com/corazawaf/coraza-spoa/issues/199)) ([a76c32f](https://github.com/corazawaf/coraza-spoa/commit/a76c32fea62f4abbb20b7f3063d4f8f85a7bda4d))
* **deps:** update all non-major dependencies to v2.18.2 in go.mod ([#229](https://github.com/corazawaf/coraza-spoa/issues/229)) ([581a429](https://github.com/corazawaf/coraza-spoa/commit/581a429be6e556291afb8e6e3261c5b2962786d5))
* **deps:** update all non-major dependencies to v2.18.3 in go.mod ([#231](https://github.com/corazawaf/coraza-spoa/issues/231)) ([fbe673b](https://github.com/corazawaf/coraza-spoa/commit/fbe673bbf258eb0bb37a512ce335562be9dc0f08))
* **deps:** update github.com/magefile/mage digest to 32e0107 ([#141](https://github.com/corazawaf/coraza-spoa/issues/141)) ([543600d](https://github.com/corazawaf/coraza-spoa/commit/543600d94a5f331786a84c00a99da17a37abad09))
* **deps:** update github.com/magefile/mage digest to 78acbaf in go.mod ([#232](https://github.com/corazawaf/coraza-spoa/issues/232)) ([7acc427](https://github.com/corazawaf/coraza-spoa/commit/7acc427f246bdb469aaba9fa75ce69ca7c660286))
* **deps:** update module github.com/corazawaf/coraza-coreruleset/v4 to v4.14.0 in go.mod ([#218](https://github.com/corazawaf/coraza-spoa/issues/218)) ([6933218](https://github.com/corazawaf/coraza-spoa/commit/6933218a419f34996d3c6e83fdae1a8ce27360bf))
* **deps:** update module github.com/corazawaf/coraza-coreruleset/v4 to v4.15.0 in go.mod ([#236](https://github.com/corazawaf/coraza-spoa/issues/236)) ([72f72ea](https://github.com/corazawaf/coraza-spoa/commit/72f72ea27c7e202386e2bca2acd85321bfaa8acb))
* **deps:** update module github.com/corazawaf/coraza/v3 to v3.2.2 ([#131](https://github.com/corazawaf/coraza-spoa/issues/131)) ([de7faf4](https://github.com/corazawaf/coraza-spoa/commit/de7faf458f041a24b1dc9c391bc7d6a9d4ea1caa))
* **deps:** update module github.com/corazawaf/coraza/v3 to v3.3.0 ([#154](https://github.com/corazawaf/coraza-spoa/issues/154)) ([87d7dde](https://github.com/corazawaf/coraza-spoa/commit/87d7dde4fa95dc03a5c7aa5cb549c94943a33024))
* **deps:** update module github.com/corazawaf/coraza/v3 to v3.3.2 ([7bb4c86](https://github.com/corazawaf/coraza-spoa/commit/7bb4c86ee715ded8e28c5fd23093a4dcb704148b))
* **deps:** update module github.com/corazawaf/coraza/v3 to v3.3.3 [security] ([39a02d6](https://github.com/corazawaf/coraza-spoa/commit/39a02d68bd636a106859f2b6702268cb7d393a9b))
* **deps:** update module github.com/dropmorepackets/haproxy-go to v0.0.6 in go.mod ([735c7af](https://github.com/corazawaf/coraza-spoa/commit/735c7afb042e89d16d1c11922fae790210560e3a))
* **deps:** update module github.com/dropmorepackets/haproxy-go to v0.0.7 in go.mod ([#226](https://github.com/corazawaf/coraza-spoa/issues/226)) ([5aa72f0](https://github.com/corazawaf/coraza-spoa/commit/5aa72f0f3d3951cfa520d4545782c6402e9d43b0))
* **deps:** update module github.com/mccutchen/go-httpbin/v2 to v2.16.0 ([#172](https://github.com/corazawaf/coraza-spoa/issues/172)) ([b0e8fdc](https://github.com/corazawaf/coraza-spoa/commit/b0e8fdc1c7d4c9c119b24ab2cf5598a4ffd5a3b9))
* **deps:** update module github.com/pires/go-proxyproto to v0.8.0 ([#119](https://github.com/corazawaf/coraza-spoa/issues/119)) ([1046c72](https://github.com/corazawaf/coraza-spoa/commit/1046c725b17f056eae5e7e3334b357ac06be4662))
* **deps:** update module github.com/rs/zerolog to v1.34.0 in go.mod ([#202](https://github.com/corazawaf/coraza-spoa/issues/202)) ([cc7b577](https://github.com/corazawaf/coraza-spoa/commit/cc7b5772da1c203a9aa8f43d696c5b348b4f1e3c))
* renovate config ([6e33b60](https://github.com/corazawaf/coraza-spoa/commit/6e33b6016b87248e339e76620d980b95258f1e9e))
* revert golang major upgrade ([3bfad4f](https://github.com/corazawaf/coraza-spoa/commit/3bfad4f53b166be1c1711e6d6510e3d0f275ab77))
* run mage lint ([7321cc4](https://github.com/corazawaf/coraza-spoa/commit/7321cc460c8297e4eb03d66aaabf1a60495eee7c))
### Miscellaneous Chores
* release 0.2.0 ([#239](https://github.com/corazawaf/coraza-spoa/issues/239)) ([e9ce67e](https://github.com/corazawaf/coraza-spoa/commit/e9ce67e2b246de124b8dc0debefa352375ce284a))
07070100000002000081A400000000000000000000000168C6803E00001462000000000000000000000000000000000000002A00000000coraza-spoa-0.4.0+git3/CODE_OF_CONDUCT.md# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
concat@coraza.io.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
07070100000003000081A400000000000000000000000168C6803E00002C5D000000000000000000000000000000000000001F00000000coraza-spoa-0.4.0+git3/LICENSE Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
07070100000004000081A400000000000000000000000168C6803E00001127000000000000000000000000000000000000002100000000coraza-spoa-0.4.0+git3/README.md<h1>
<img src="https://coraza.io/images/logo_shield_only.png" align="left" height="46px" alt=""/>
<span>Coraza SPOA - HAProxy Web Application Firewall</span>
</h1>
[](https://github.com/corazawaf/coraza-spoa/actions/workflows/lint.yaml)
[](https://github.com/corazawaf/coraza-spoa/actions/workflows/codeql.yaml)
Coraza SPOA is a system daemon which brings the Coraza Web Application Firewall (WAF) as a backing service for HAProxy. It is written in Go, Coraza supports ModSecurity SecLang rulesets and is 100% compatible with the OWASP Core Rule Set v4.
HAProxy includes a [Stream Processing Offload Engine](https://www.haproxy.com/blog/extending-haproxy-with-the-stream-processing-offload-engine) [SPOE](https://raw.githubusercontent.com/haproxy/haproxy/master/doc/SPOE.txt) to offload request processing to a Stream Processing Offload Agent (SPOA). Coraza SPOA embeds the [Coraza Engine](https://github.com/corazawaf/coraza), loads the ruleset and filters http requests or application responses which are passed forwarded by HAProxy for inspection.
## Compilation
### Build
The command `go run mage.go build` will compile the source code and produce the executable file `coraza-spoa`.
## Configuration
## Coraza SPOA
The example configuration file is [example/coraza-spoa.yaml](https://github.com/corazawaf/coraza-spoa/blob/main/example/coraza-spoa.yaml), you can copy it and modify the related configuration information. You can start the service by running the command:
```
coraza-spoa -f /etc/coraza-spoa/coraza-spoa.yaml
```
You will also want to download & extract the [OWASP Core Ruleset]( https://github.com/coreruleset/coreruleset/releases) (version 4+ supported) to the `/etc/coraza-spoa` directory.
## HAProxy SPOE
Configure HAProxy to exchange messages with the SPOA. The example SPOE configuration file is [coraza.cfg](https://github.com/corazawaf/coraza-spoa/blob/main/example/haproxy/coraza.cfg), you can copy it and modify the related configuration information. Default directory to place the config is `/etc/haproxy/coraza.cfg`.
```ini
# /etc/haproxy/coraza.cfg
spoe-agent coraza-agent
...
use-backend coraza-spoa
spoe-message coraza-req
args app=str(sample_app) id=unique-id src-ip=src ...
event on-frontend-http-request
```
The application name from `config.yaml` must match the `app=` name.
The backend defined in `use-backend` must match a `haproxy.cfg` backend which directs requests to the SPOA daemon reachable via `127.0.0.1:9000`.
Instead of the hard coded application name `str(sample_app)` you can use some HAProxy variables. For example, frontend name `fe_name`.
## HAProxy
Configure HAProxy with a frontend, which contains a `filter` statement to forward requests to the SPOA and deny based on the returned action. Also add a backend section, which is referenced by use-backend in `coraza.cfg`.
```haproxy
# /etc/haproxy/haproxy.cfg
frontend web
filter spoe engine coraza config /etc/haproxy/coraza.cfg
...
http-request deny deny_status 403 hdr waf-block "request" if { var(txn.coraza.action) -m str deny }
...
backend coraza-spoa
mode tcp
server s1 127.0.0.1:9000
```
A comprehensive HAProxy configuration example can be found in [example/haproxy/coraza.cfg](https://github.com/corazawaf/coraza-spoa/blob/main/example/haproxy/coraza.cfg).
Because, in the SPOE configuration file (coraza.cfg), we declare to use the backend [coraza-spoa](https://github.com/corazawaf/coraza-spoa/blob/main/example/haproxy/coraza.cfg#L14) to communicate with the service, so we need also to define it in the [HAProxy file](https://github.com/corazawaf/coraza-spoa/blob/main/example/haproxy/haproxy.cfg#L37):
If you intend to access coraza-spoa service from another machine, remember to change the binding networking directives (IPAddressAllow/IPAddressDeny) in [contrib/coraza-spoa.service](https://github.com/corazawaf/coraza-spoa/blob/main/contrib/coraza-spoa.service)
## Docker
- Build the coraza-spoa image `cd ./example ; docker compose build`
- Run haproxy, coraza-spoa and a mock server `docker compose up`
- Perform a request which gets blocked by the WAF: `curl http://localhost:8080/\?x\=/etc/passwd`
07070100000005000081A400000000000000000000000168C6803E00001685000000000000000000000000000000000000002100000000coraza-spoa-0.4.0+git3/config.gopackage main
import (
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"time"
"github.com/fsnotify/fsnotify"
"github.com/rs/zerolog"
"gopkg.in/yaml.v3"
"github.com/corazawaf/coraza-spoa/internal"
)
func readConfig() (*config, error) {
open, err := os.Open(configPath)
if err != nil {
return nil, err
}
defer open.Close()
d := yaml.NewDecoder(open)
d.KnownFields(true)
var cfg config
if err := d.Decode(&cfg); err != nil {
return nil, err
}
if len(cfg.Applications) == 0 {
globalLogger.Warn().Msg("no applications defined")
}
if cfg.DefaultApplication != "" {
var found bool
for _, app := range cfg.Applications {
if app.Name == cfg.DefaultApplication {
globalLogger.Debug().Str("app", cfg.DefaultApplication).Msg("configured as default application")
found = true
break
}
}
if !found {
return nil, fmt.Errorf("default application not found among defined applications: %s", cfg.DefaultApplication)
}
}
return &cfg, nil
}
type config struct {
Bind string `yaml:"bind"`
Log logConfig `yaml:",inline"`
DefaultApplication string `yaml:"default_application"`
Applications []struct {
Log logConfig `yaml:",inline"`
Name string `yaml:"name"`
Directives string `yaml:"directives"`
ResponseCheck bool `yaml:"response_check"`
TransactionTTLMS int `yaml:"transaction_ttl_ms"`
} `yaml:"applications"`
}
func (c config) networkAddressFromBind() (network string, address string) {
bindUrl, err := url.Parse(c.Bind)
if err == nil {
return bindUrl.Scheme, bindUrl.Path
}
return "tcp", c.Bind
}
func (c *config) reloadConfig(a *internal.Agent) (*config, error) {
newCfg, err := readConfig()
if err != nil {
return nil, fmt.Errorf("error loading configuration: %w", err)
}
if c.Log != newCfg.Log {
newLogger, err := newCfg.Log.newLogger()
if err != nil {
return nil, fmt.Errorf("error creating new global logger: %w", err)
}
globalLogger = newLogger
}
if c.Bind != newCfg.Bind {
return nil, fmt.Errorf("changing bind is not supported yet")
}
apps, err := newCfg.newApplications()
if err != nil {
return nil, fmt.Errorf("error applying configuration: %w", err)
}
a.ReplaceApplications(apps)
globalLogger.Info().Msg("Configuration successfully reloaded")
return newCfg, nil
}
func (c *config) watchConfig(a *internal.Agent) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return fmt.Errorf("failed to create fsnotify watcher: %w", err)
}
defer watcher.Close()
// configmap mounts are symlinks
// so we have to watch the parent directory instead of the file itself
configDir := filepath.Dir(configPath)
err = watcher.Add(configDir)
if err != nil {
return fmt.Errorf("failed to add config directory to fsnotify watcher: %w", err)
}
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return nil
}
// on configmap change, the directory symlink is recreated
// so we have to catch this event and readd the directory back to watcher
if event.Op == fsnotify.Remove {
globalLogger.Info().Msg("Config directory updated, reloading configuration...")
err = watcher.Remove(configDir)
if err != nil {
return fmt.Errorf("failed to remove config directory from fsnotify watcher: %w", err)
}
err = watcher.Add(configDir)
if err != nil {
return fmt.Errorf("failed to add config directory to fsnotify watcher: %w", err)
}
newCfg, err := c.reloadConfig(a)
if err != nil {
globalLogger.Error().Err(err).Msg("Failed to reload configuration, using old configuration")
continue
}
c = newCfg
}
case err, ok := <-watcher.Errors:
if !ok {
return nil
}
globalLogger.Error().Err(err).Msg("Error watching config directory")
}
}
}
func (c config) newApplications() (map[string]*internal.Application, error) {
allApps := make(map[string]*internal.Application)
for name, a := range c.Applications {
logger, err := a.Log.newLogger()
if err != nil {
return nil, fmt.Errorf("creating logger for application %q: %v", name, err)
}
appConfig := internal.AppConfig{
Logger: logger,
Directives: a.Directives,
ResponseCheck: a.ResponseCheck,
LogFormat: a.Log.Format,
TransactionTTL: time.Duration(a.TransactionTTLMS) * time.Millisecond,
}
application, err := appConfig.NewApplication()
if err != nil {
return nil, fmt.Errorf("initializing application %q: %v", name, err)
}
allApps[a.Name] = application
}
return allApps, nil
}
type logConfig struct {
Level string `yaml:"log_level"`
File string `yaml:"log_file"`
Format string `yaml:"log_format"`
}
func (lc logConfig) outputWriter() (io.Writer, error) {
var out io.Writer
if lc.File == "" || lc.File == "/dev/stdout" {
out = os.Stdout
} else if lc.File == "/dev/stderr" {
out = os.Stderr
} else if lc.File == "/dev/null" {
out = io.Discard
} else {
// TODO: Close the handle if not used anymore.
// Currently these are leaked as soon as we reload.
f, err := os.OpenFile(lc.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
out = f
}
return out, nil
}
func (lc logConfig) newLogger() (zerolog.Logger, error) {
out, err := lc.outputWriter()
if err != nil {
return globalLogger, err
}
switch lc.Format {
case "console":
out = zerolog.ConsoleWriter{
Out: out,
}
case "json":
default:
return globalLogger, fmt.Errorf("unknown log format: %v", lc.Format)
}
if lc.Level == "" {
lc.Level = "info"
}
lvl, err := zerolog.ParseLevel(lc.Level)
if err != nil {
return globalLogger, err
}
return zerolog.New(out).Level(lvl).With().Timestamp().Logger(), nil
}
07070100000006000041ED00000000000000000000000268C6803E00000000000000000000000000000000000000000000001F00000000coraza-spoa-0.4.0+git3/contrib07070100000007000081A400000000000000000000000168C6803E000000E3000000000000000000000000000000000000003500000000coraza-spoa-0.4.0+git3/contrib/coraza-spoa.logrotate/var/log/coraza-spoa/*.log {
daily
rotate 7
missingok
notifempty
compress
delaycompress
postrotate
[ ! -x /usr/lib/rsyslog/rsyslog-rotate ] || /usr/lib/rsyslog/rsyslog-rotate
endscript
}
07070100000008000081A400000000000000000000000168C6803E0000025F000000000000000000000000000000000000003400000000coraza-spoa-0.4.0+git3/contrib/coraza-spoa.postinst#!/bin/sh
set -e
# add unprivileged user & group for the coraza-spoa
addgroup --quiet --system coraza-spoa || true
adduser --quiet --system --ingroup coraza-spoa --no-create-home --home /nonexistent --disabled-password coraza-spoa || true
if [ ! -d /var/log/coraza-spoa ]; then
mkdir -p /var/log/coraza-spoa /var/log/coraza-spoa/audit
touch /var/log/coraza-spoa/server.log /var/log/coraza-spoa/error.log \
/var/log/coraza-spoa/audit.log /var/log/coraza-spoa/debug.log
fi
chown -R coraza-spoa:adm /var/log/coraza-spoa 2> /dev/null || true
chmod 755 /var/log/coraza-spoa 2> /dev/null || true
07070100000009000081A400000000000000000000000168C6803E00002603000000000000000000000000000000000000003300000000coraza-spoa-0.4.0+git3/contrib/coraza-spoa.service[Unit]
Description=Coraza WAF SPOA Daemon
Documentation=https://www.coraza.io
[Service]
ExecStart=/usr/bin/coraza-spoa -config=/etc/coraza-spoa/config.yaml
WorkingDirectory=/
Restart=always
Type=exec
User=coraza-spoa
Group=coraza-spoa
# Hardening
AmbientCapabilities=
MountFlags=private
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectProc=noaccess
ProtectClock=yes
ProtectHostname=yes
ProtectSystem=strict
RestrictSUIDSGID=true
RestrictRealtime=true
SecureBits=no-setuid-fixup-locked noroot-locked
TemporaryFileSystem=/etc
TemporaryFileSystem=/var
BindReadOnlyPaths=-/etc/ca-certificates
BindReadOnlyPaths=-/etc/crypto-policies
BindReadOnlyPaths=-/etc/fdns
BindReadOnlyPaths=-/etc/ld.so.cache
BindReadOnlyPaths=-/etc/ld.so.preload
BindReadOnlyPaths=-/etc/localtime
BindReadOnlyPaths=-/etc/nsswitch.conf
BindReadOnlyPaths=-/etc/passwd
BindReadOnlyPaths=-/etc/pki
BindReadOnlyPaths=-/etc/ssl
BindReadOnlyPaths=-/etc/coraza-spoa
BindPaths=-/var/log/coraza-spoa
InaccessiblePaths=-/opt
InaccessiblePaths=-/srv
#InaccessiblePaths=-/bin
InaccessiblePaths=-/bin/bash
inaccessiblepaths=-/bin/find
InaccessiblePaths=-/bin/less
InaccessiblePaths=-/bin/zcat
InaccessiblePaths=-/bin/rm
InaccessiblePaths=-/bin/readlink
InaccessiblePaths=-/bin/readpath
InaccessiblePaths=-/sbin
InaccessiblePaths=-/efi
InaccessiblePaths=-/run/media
InaccessiblePaths=-/run/mount
InaccessiblePaths=-/efi
InaccessiblePaths=-/boot
InaccessiblePaths=-/dev/kmsg
InaccessiblePaths=-/dev/port
InaccessiblePaths=-/lib/modules
InaccessiblePaths=-/lost+found
InaccessiblePaths=-/proc/bus
InaccessiblePaths=-/proc/config.gz
InaccessiblePaths=-/usr/bin/alsaloop
InaccessiblePaths=-/usr/bin/alsamixer
InaccessiblePaths=-/usr/bin/alsatplg
InaccessiblePaths=-/usr/bin/alsaucm
InaccessiblePaths=-/usr/bin/alsaunmute
InaccessiblePaths=-/usr/bin/attr
InaccessiblePaths=-/usr/bin/balooctl
InaccessiblePaths=-/usr/bin/bash
InaccessiblePaths=-/usr/bin/bootctl
InaccessiblePaths=-/usr/bin/busctl
InaccessiblePaths=-/usr/bin/chacl
InaccessiblePaths=-/usr/bin/chattr
InaccessiblePaths=-/usr/bin/cmp
InaccessiblePaths=-/usr/bin/coredumpctl
InaccessiblePaths=-/usr/bin/crontab
InaccessiblePaths=-/usr/bin/csh
InaccessiblePaths=-/usr/bin/dash
InaccessiblePaths=-/usr/bin/dd
InaccessiblePaths=-/usr/bin/df
InaccessiblePaths=-/usr/bin/diff
InaccessiblePaths=-/usr/bin/diff3
InaccessiblePaths=-/usr/bin/dmesg
InaccessiblePaths=-/usr/bin/dnf
InaccessiblePaths=-/usr/bin/dotty
InaccessiblePaths=-/usr/bin/dracut
InaccessiblePaths=-/usr/bin/evmctl
InaccessiblePaths=-/usr/bin/free
InaccessiblePaths=-/usr/bin/ftp
InaccessiblePaths=-/usr/bin/getfacl
InaccessiblePaths=-/usr/bin/getfattr
InaccessiblePaths=-/usr/bin/grotty
InaccessiblePaths=-/usr/bin/grub2-file
InaccessiblePaths=-/usr/bin/grub2-menulst2cfg
InaccessiblePaths=-/usr/bin/grub2-mkimage
InaccessiblePaths=-/usr/bin/grub2-mkrelpath
InaccessiblePaths=-/usr/bin/grub2-render-label
InaccessiblePaths=-/usr/bin/grub2-script-check
InaccessiblePaths=-/usr/bin/hostnamectl
InaccessiblePaths=-/usr/bin/htop
InaccessiblePaths=-/usr/bin/ipcmk
InaccessiblePaths=-/usr/bin/journalctl
InaccessiblePaths=-/usr/bin/keyctl
InaccessiblePaths=-/usr/bin/kill
InaccessiblePaths=-/usr/bin/killall
InaccessiblePaths=-/usr/bin/ksh
InaccessiblePaths=-/usr/bin/last
InaccessiblePaths=-/usr/bin/localectl
InaccessiblePaths=-/usr/bin/locate
InaccessiblePaths=-/usr/bin/loginctl
InaccessiblePaths=-/usr/bin/ls
InaccessiblePaths=-/usr/bin/lsattr
InaccessiblePaths=-/usr/bin/lsb_release
InaccessiblePaths=-/usr/bin/lsblk
InaccessiblePaths=-/usr/bin/lscpu
InaccessiblePaths=-/usr/bin/lsdiff
InaccessiblePaths=-/usr/bin/lsinitrd
InaccessiblePaths=-/usr/bin/lsipc
InaccessiblePaths=-/usr/bin/lslocks
InaccessiblePaths=-/usr/bin/lslogins
InaccessiblePaths=-/usr/bin/lsmem
InaccessiblePaths=-/usr/bin/lsns
InaccessiblePaths=-/usr/bin/lsof
InaccessiblePaths=-/usr/bin/lsscsi
InaccessiblePaths=-/usr/bin/lsusb
InaccessiblePaths=-/usr/bin/lua
InaccessiblePaths=-/usr/bin/lynis
InaccessiblePaths=-/usr/bin/mail
InaccessiblePaths=-/usr/bin/mkfifo
InaccessiblePaths=-/usr/bin/mkinitrd
InaccessiblePaths=-/usr/bin/mkisofs
InaccessiblePaths=-/usr/bin/mknod
InaccessiblePaths=-/usr/bin/mount
InaccessiblePaths=-/usr/bin/mountpoint
InaccessiblePaths=-/usr/bin/nc
InaccessiblePaths=-/usr/bin/netcap
InaccessiblePaths=-/usr/bin/netstat
InaccessiblePaths=-/usr/bin/netstat-nat
InaccessiblePaths=-/usr/bin/networkctl
InaccessiblePaths=-/usr/bin/nmap
InaccessiblePaths=-/usr/bin/nping
InaccessiblePaths=-/usr/bin/nsenter
InaccessiblePaths=-/usr/bin/pactl
InaccessiblePaths=-/usr/bin/panelctl
InaccessiblePaths=-/usr/bin/passwd
InaccessiblePaths=-/usr/bin/peekfd
InaccessiblePaths=-/usr/bin/pgrep
InaccessiblePaths=-/usr/bin/pidof
InaccessiblePaths=-/usr/bin/ping
InaccessiblePaths=-/usr/bin/pkill
InaccessiblePaths=-/usr/bin/pkttyagent
InaccessiblePaths=-/usr/bin/pmap
InaccessiblePaths=-/usr/bin/portablectl
InaccessiblePaths=-/usr/bin/prtstat
InaccessiblePaths=-/usr/bin/ps
InaccessiblePaths=-/usr/bin/pslog
InaccessiblePaths=-/usr/bin/pstree
InaccessiblePaths=-/usr/bin/pstree.x11
InaccessiblePaths=-/usr/bin/pulseaudio
InaccessiblePaths=-/usr/bin/pwdx
InaccessiblePaths=-/usr/bin/python
InaccessiblePaths=-/usr/bin/python2
InaccessiblePaths=-/usr/bin/python3
InaccessiblePaths=-/usr/bin/python3.9
InaccessiblePaths=-/usr/bin/resolvectl
InaccessiblePaths=-/usr/bin/rkhunter
InaccessiblePaths=-/usr/bin/rpm
InaccessiblePaths=-/usr/bin/rsync
InaccessiblePaths=-/usr/bin/ruby
InaccessiblePaths=-/usr/bin/run-parts
InaccessiblePaths=-/usr/bin/scp
InaccessiblePaths=-/usr/bin/screen
InaccessiblePaths=-/usr/bin/sdiff
InaccessiblePaths=-/usr/bin/setarch
InaccessiblePaths=-/usr/bin/setcifsacl
InaccessiblePaths=-/usr/bin/setfacl
InaccessiblePaths=-/usr/bin/setfattr
InaccessiblePaths=-/usr/bin/setpriv
InaccessiblePaths=-/usr/bin/setsid
InaccessiblePaths=-/usr/bin/setterm
InaccessiblePaths=-/usr/bin/setxkbmap
InaccessiblePaths=-/usr/bin/sftp
InaccessiblePaths=-/usr/bin/sh
InaccessiblePaths=-/usr/bin/skill
InaccessiblePaths=-/usr/bin/slabtop
InaccessiblePaths=-/usr/bin/snice
InaccessiblePaths=-/usr/bin/ssh
InaccessiblePaths=-/usr/bin/ssh-add
InaccessiblePaths=-/usr/bin/ssh-agent
InaccessiblePaths=-/usr/bin/ssh-copy-id
InaccessiblePaths=-/usr/bin/ssh-keyscan
InaccessiblePaths=-/usr/bin/strace
InaccessiblePaths=-/usr/bin/strace-log-merg
InaccessiblePaths=-/usr/bin/strings
InaccessiblePaths=-/usr/bin/stty
InaccessiblePaths=-/usr/bin/su
InaccessiblePaths=-/usr/bin/sudo
InaccessiblePaths=-/usr/bin/systemctl
InaccessiblePaths=-/usr/bin/systemd-tty-ask-password-agent
InaccessiblePaths=-/usr/bin/tcl
InaccessiblePaths=-/usr/bin/tcptraceroute
InaccessiblePaths=-/usr/bin/tcsh
InaccessiblePaths=-/usr/bin/telnet
InaccessiblePaths=-/usr/bin/timedatectl
InaccessiblePaths=-/usr/bin/tload
InaccessiblePaths=-/usr/bin/tmux
InaccessiblePaths=-/usr/bin/top
InaccessiblePaths=-/usr/bin/touch
InaccessiblePaths=-/usr/bin/tracepath
InaccessiblePaths=-/usr/bin/traceroute
InaccessiblePaths=-/usr/bin/traceroute6
InaccessiblePaths=-/usr/bin/tricklectl
InaccessiblePaths=-/usr/bin/tty
InaccessiblePaths=-/usr/bin/udevadm
InaccessiblePaths=-/usr/bin/udisksctl
InaccessiblePaths=-/usr/bin/umount
InaccessiblePaths=-/usr/bin/uname
InaccessiblePaths=-/usr/bin/unlink
InaccessiblePaths=-/usr/bin/updatedb
InaccessiblePaths=-/usr/bin/uptime
InaccessiblePaths=-/usr/bin/users
InaccessiblePaths=-/usr/bin/vi
InaccessiblePaths=-/usr/bin/vim
InaccessiblePaths=-/usr/bin/vim.nox
InaccessiblePaths=-/usr/bin/vim.tiny
InaccessiblePaths=-/usr/bin/vimtutor
InaccessiblePaths=-/usr/bin/vmware-checkvm
InaccessiblePaths=-/usr/bin/vmware-namespace-cmd
InaccessiblePaths=-/usr/bin/vmware-rpctool
InaccessiblePaths=-/usr/bin/vmware-toolbox-cmd
InaccessiblePaths=-/usr/bin/vmware-xferlogs
InaccessiblePaths=-/usr/bin/w
InaccessiblePaths=-/usr/bin/wall
InaccessiblePaths=-/usr/bin/watch
InaccessiblePaths=-/usr/bin/wdctl
InaccessiblePaths=-/usr/bin/wg
InaccessiblePaths=-/usr/bin/wget
InaccessiblePaths=-/usr/bin/who
InaccessiblePaths=-/usr/bin/whoami
InaccessiblePaths=-/usr/bin/zsh
InaccessiblePaths=-/usr/local
InaccessiblePaths=-/usr/sbin
InaccessiblePaths=-/proc/irq
InaccessiblePaths=-/proc/kallsyms
InaccessiblePaths=-/proc/kcore
InaccessiblePaths=-/proc/kmem
#*InaccessiblePaths=-/proc/kmsg
InaccessiblePaths=-/proc/mem
InaccessiblePaths=-/proc/sched_debug
InaccessiblePaths=-/proc/sys/efi/vars
InaccessiblePaths=-/proc/sys/fs/binfmt_misc
#*InaccessiblePaths=-/proc/sys/kernel/core_pattern
InaccessiblePaths=-/proc/sys/kernel/hotplug
#*InaccessiblePaths=-/proc/sys/kernel/modprobe
InaccessiblePaths=-/proc/sys/security
#*InaccessiblePaths=-/proc/sys/vm/panic_on_oom
InaccessiblePaths=-/proc/sysrq-trigger
InaccessiblePaths=-/proc/timer_list
InaccessiblePaths=-/proc/timer_stats
InaccessiblePaths=-/selinux
InaccessiblePaths=-/sys/firmware
InaccessiblePaths=-/sys/fs
InaccessiblePaths=-/sys/hypervisor
InaccessiblePaths=-/sys/kernel/debug
InaccessiblePaths=-/sys/kernel/uevent_helper
InaccessiblePaths=-/sys/kernel/vmcoreinfo
InaccessiblePaths=-/sys/module
InaccessiblePaths=-/sys/power
#*InaccessiblePaths=-/usr/lib/debug
InaccessiblePaths=-/usr/src/linux
LockPersonality=true
LogsDirectory=coraza-spoa
ConfigurationDirectory=coraza-spoa
# coraza-geoip
ReadOnlyPaths=/usr/share/GeoIP
#ReadOnlyPaths=/proc
MemoryDenyWriteExecute=yes
NoNewPrivileges=true
ProtectHome=true
PrivateDevices=true
PrivateUsers=true
PrivateTmp=true
RemoveIPC=true
RestrictAddressFamilies=AF_INET AF_INET6
#RestrictNamespaces=uts ipc pid user cgroup
SystemCallArchitectures=native
SystemCallFilter=@system-service -@setuid -@ipc -@mount
IPAddressDeny=any
IPAddressAllow=localhost
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
0707010000000A000041ED00000000000000000000000268C6803E00000000000000000000000000000000000000000000001F00000000coraza-spoa-0.4.0+git3/example0707010000000B000081A400000000000000000000000168C6803E000003DA000000000000000000000000000000000000002A00000000coraza-spoa-0.4.0+git3/example/Dockerfile# Copyright 2023 The OWASP Coraza contributors
# SPDX-License-Identifier: Apache-2.0
FROM golang:1.23@sha256:60deed95d3888cc5e4d9ff8a10c54e5edc008c6ae3fba6187be6fb592e19e8c0 AS build
WORKDIR /go/src/app
COPY . .
RUN go mod download
RUN go vet -v ./...
RUN CGO_ENABLED=0 go build -o /go/bin/coraza-spoa
FROM gcr.io/distroless/static-debian11@sha256:1dbe426d60caed5d19597532a2d74c8056cd7b1674042b88f7328690b5ead8ed
LABEL org.opencontainers.image.authors="The OWASP Coraza contributors" \
org.opencontainers.image.description="OWASP Coraza WAF (Haproxy SPOA)" \
org.opencontainers.image.documentation="https://coraza.io/connectors/coraza-spoa/" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.source="https://github.com/corazawaf/coraza-spoa" \
org.opencontainers.image.title="coraza-spoa"
COPY --from=build /go/bin/coraza-spoa /
COPY ./example/coraza-spoa.yaml /config.yaml
CMD ["/coraza-spoa", "--config", "/config.yaml"]0707010000000C000081A400000000000000000000000168C6803E00000477000000000000000000000000000000000000003000000000coraza-spoa-0.4.0+git3/example/coraza-spoa.yaml# The SPOA server bind address
bind: 0.0.0.0:9000
# The log level configuration, one of: debug/info/warn/error/panic/fatal
log_level: info
# The log file path
log_file: /dev/stdout
# The log format, one of: console/json
log_format: console
# Optional default application to use when the app from the request
# does not match any of the declared application names
default_application: sample_app
applications:
# name is used as key to identify the directives
- name: sample_app
# Some example rules.
# The built-in OWASP CRS rules are available in @owasp_crs/
directives: |
Include @coraza.conf-recommended
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
SecRuleEngine On
# HAProxy configured to send requests only, that means no cache required
response_check: false
# The transaction cache lifetime in milliseconds (60000ms = 60s)
transaction_ttl_ms: 60000
# The log level configuration, one of: debug/info/warn/error/panic/fatal
log_level: info
# The log file path
log_file: /dev/stdout
# The log format, one of: console/json
log_format: console
0707010000000D000081A400000000000000000000000168C6803E0000038D000000000000000000000000000000000000003300000000coraza-spoa-0.4.0+git3/example/docker-compose.yamlversion: "3.9"
services:
httpbin:
image: mccutchen/go-httpbin:2.18.3@sha256:3992f3763e9ce5a4307eae0a869a78b4df3931dc8feba74ab823dd2444af6a6b
environment:
- MAX_BODY_SIZE=15728640 # 15 MiB
command: [ "/bin/go-httpbin", "-port", "8081" ]
ports:
- "8081:8081"
coraza-spoa:
restart: unless-stopped
build:
context: ..
dockerfile: ./example/Dockerfile
ports:
- "9000:9000"
haproxy:
restart: unless-stopped
image: haproxy:2.9-alpine@sha256:3e29449a6beed63262e36104adf531b4e41b359f61937303f5ea8607987b3748
ports: [ "8080:80", "8443:443", "8082:8082"]
depends_on:
- httpbin
links:
- "coraza-spoa:coraza-spoa"
- "httpbin:httpbin"
volumes:
- type: bind
source: ./haproxy/
target: /usr/local/etc/haproxy
environment:
- BACKEND_HOST=httpbin:8081
- CORAZA_SPOA_HOST=coraza-spoa0707010000000E000041ED00000000000000000000000268C6803E00000000000000000000000000000000000000000000002700000000coraza-spoa-0.4.0+git3/example/haproxy0707010000000F000081A400000000000000000000000168C6803E000003E5000000000000000000000000000000000000003200000000coraza-spoa-0.4.0+git3/example/haproxy/coraza.cfg# https://github.com/haproxy/haproxy/blob/master/doc/SPOE.txt
# /usr/local/etc/haproxy/coraza.cfg
[coraza]
spoe-agent coraza-agent
# Uncomment the following line, to process responses also.
#messages coraza-res
groups coraza-req
option var-prefix coraza
option set-on-error error
timeout hello 2s
timeout idle 2m
timeout processing 500ms
use-backend coraza-spoa
log global
spoe-message coraza-req
# Arguments are required to be in this order
args app=var(txn.coraza.app) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
spoe-message coraza-res
# Arguments are required to be in this order
args app=var(txn.coraza.app) id=var(txn.coraza.id) version=res.ver status=status headers=res.hdrs body=res.body
event on-http-response
spoe-group coraza-req
messages coraza-req
07070100000010000081A400000000000000000000000168C6803E00000909000000000000000000000000000000000000003300000000coraza-spoa-0.4.0+git3/example/haproxy/haproxy.cfg# https://docs.haproxy.org/
global
log stdout format raw local0
defaults
log global
option httplog
timeout client 1m
timeout server 1m
timeout connect 10s
frontend default
mode http
bind *:80
log-format "%ci:%cp\ [%t]\ %ft\ %b/%s\ %Th/%Ti/%TR/%Tq/%Tw/%Tc/%Tr/%Tt\ %ST\ %B\ %CC\ %CS\ %tsc\ %ac/%fc/%bc/%sc/%rc\ %sq/%bq\ %hr\ %hs\ %{+Q}r\ %[var(txn.coraza.id)]\ spoa-error:\ %[var(txn.coraza.error)]\ waf-hit:\ %[var(txn.coraza.status)]"
# Emulate Apache behavior by only allowing http 1.0, 1.1, 2.0
http-request deny deny_status 400 if !HTTP
http-request deny deny_status 400 if !HTTP_1.0 !HTTP_1.1 !HTTP_2.0
# Set coraza app in HAProxy config to allow customized configs per host.
# You can also just leave this as is or even replace the use of a variable
# inside the coraza.cfg.
http-request set-var(txn.coraza.app) str(sample_app)
# !! Every http-request line will be executed before this !!
# Execute coraza request check.
filter spoe engine coraza config /usr/local/etc/haproxy/coraza.cfg
http-request send-spoe-group coraza coraza-req
# Currently haproxy cannot use variables to set the code or deny_status, so this needs to be manually configured here
http-request redirect code 302 location %[var(txn.coraza.data)] if { var(txn.coraza.action) -m str redirect }
http-response redirect code 302 location %[var(txn.coraza.data)] if { var(txn.coraza.action) -m str redirect }
http-request deny deny_status 403 hdr waf-block "request" if { var(txn.coraza.action) -m str deny }
http-response deny deny_status 403 hdr waf-block "response" if { var(txn.coraza.action) -m str deny }
http-request silent-drop if { var(txn.coraza.action) -m str drop }
http-response silent-drop if { var(txn.coraza.action) -m str drop }
# Deny in case of an error, when processing with the Coraza SPOA
http-request deny deny_status 500 if { var(txn.coraza.error) -m int gt 0 }
http-response deny deny_status 500 if { var(txn.coraza.error) -m int gt 0 }
use_backend httpbin_backend
resolvers host_dns
parse-resolv-conf
backend httpbin_backend
mode http
server backend $BACKEND_HOST
backend coraza-spoa
option spop-check
mode tcp
server coraza_spoa coraza-spoa:9000 check
07070100000011000041ED00000000000000000000000268C6803E00000000000000000000000000000000000000000000001B00000000coraza-spoa-0.4.0+git3/ftw07070100000012000081A400000000000000000000000168C6803E000003D6000000000000000000000000000000000000003200000000coraza-spoa-0.4.0+git3/ftw/Dockerfile.coraza_spoa# Copyright 2023 The OWASP Coraza contributors
# SPDX-License-Identifier: Apache-2.0
FROM golang:1.23@sha256:60deed95d3888cc5e4d9ff8a10c54e5edc008c6ae3fba6187be6fb592e19e8c0 AS build
WORKDIR /go/src/app
COPY . .
RUN go mod download
RUN go vet -v ./...
RUN CGO_ENABLED=0 go build -o /go/bin/coraza-spoa
FROM gcr.io/distroless/static-debian12@sha256:87bce11be0af225e4ca761c40babb06d6d559f5767fbf7dc3c47f0f1a466b92c
LABEL org.opencontainers.image.authors="The OWASP Coraza contributors" \
org.opencontainers.image.description="OWASP Coraza WAF (Haproxy SPOA)" \
org.opencontainers.image.documentation="https://coraza.io/connectors/coraza-spoa/" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.source="https://github.com/corazawaf/coraza-spoa" \
org.opencontainers.image.title="coraza-spoa"
COPY --from=build /go/bin/coraza-spoa /
COPY ./ftw/coraza-spoa.yaml /config.yaml
CMD ["/coraza-spoa", "--config", "/config.yaml"]07070100000013000081A400000000000000000000000168C6803E00000321000000000000000000000000000000000000002A00000000coraza-spoa-0.4.0+git3/ftw/Dockerfile.ftw# Copyright 2025 The OWASP Coraza contributors
# SPDX-License-Identifier: Apache-2.0
FROM ghcr.io/coreruleset/go-ftw:1.3.0@sha256:99e5d772dc0292a1685b23cff2ba40463db71ab96ebf98aa9cf517b641300254
RUN apk update && apk add curl
WORKDIR /workspace
# TODOs:
# - update when new CRS version is tagged: https://github.com/coreruleset/coreruleset/archive/refs/tags/v4.5.0.tar.gz
# - keep it aligned with the github.com/corazawaf/coraza-coreruleset/v4 dependency version used
ENV CRS_VERSION=4.15.0
ADD https://github.com/coreruleset/coreruleset/archive/refs/tags/v${CRS_VERSION}.tar.gz /workspace/coreruleset/
RUN cd coreruleset && tar -xf v${CRS_VERSION}.tar.gz --strip-components 1
COPY ftw.yml /workspace/ftw.yml
COPY tests.sh /workspace/tests.sh
ENTRYPOINT ["sh"]
CMD ["-c", "/workspace/tests.sh"]07070100000014000081A400000000000000000000000168C6803E000005D4000000000000000000000000000000000000002C00000000coraza-spoa-0.4.0+git3/ftw/coraza-spoa.yaml---
bind: 0.0.0.0:9000
log_level: info
log_file: /build/ftw-spoa.log
log_format: console
applications:
- name: ftw
directives: |
Include @coraza.conf-recommended
# log details on failures
SecAuditLog /build/ftw-audit.log
SecAuditLogRelevantStatus "^(?:5)"
# FTW config
SecDefaultAction "phase:3,log,auditlog,pass"
SecDefaultAction "phase:4,log,auditlog,pass"
SecDefaultAction "phase:5,log,auditlog,pass"
SecDebugLogLevel 3
SecAction "id:900005,\
phase:1,\
nolog,\
pass,\
ctl:ruleEngine=DetectionOnly,\
ctl:ruleRemoveById=910000,\
setvar:tx.blocking_paranoia_level=4,\
setvar:tx.crs_validate_utf8_encoding=1,\
setvar:tx.arg_name_length=100,\
setvar:tx.arg_length=400,\
setvar:tx.total_arg_length=64000,\
setvar:tx.max_num_args=255,\
setvar:tx.max_file_size=64100,\
setvar:tx.combined_file_sizes=65535"
SecRule REQUEST_HEADERS:X-CRS-Test "@rx ^.*$" "id:999999,\
phase:1,\
pass,\
t:none,\
log,\
msg:'X-CRS-Test %{MATCHED_VAR}',\
ctl:ruleRemoveById=1-999999"
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
response_check: true
transaction_ttl_ms: 60000
log_level: error # Printing only logs at error level to reduce what ftw has to parse
log_file: /build/ftw.log
log_format: console
07070100000015000081A400000000000000000000000168C6803E000005F2000000000000000000000000000000000000002E00000000coraza-spoa-0.4.0+git3/ftw/docker-compose.yml---
services:
backend:
image: ghcr.io/coreruleset/albedo:0.2.0@sha256:bc9b7e4f83a5268ccd2b4d1d26e926b6324e20ff1d91281c339ad82e55f5bab2
command: ['--port', '8081']
ports:
- 8081:8081
# clean existing logs, enable haproxy to write logs, allow devs to manage logs without sudo
prepare-logs:
image: alpine:3.22@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
command:
- /bin/sh
- -c
- rm -f /build/ftw* && touch /build/ftw.log /build/ftw-haproxy.log /build/ftw-haproxy.log /build/ftw-audit.log && chmod 666 /build/ftw*
volumes:
- ../build:/build:rw
coraza-spoa:
depends_on:
- prepare-logs
build:
context: ..
dockerfile: ./ftw/Dockerfile.coraza_spoa
network: host
volumes:
- ../build:/build:rw
ports:
- 9000:9000
haproxy:
depends_on:
- prepare-logs
- backend
image: "haproxy:${FTW_HAPROXY_VERSION:-3.0}-alpine"
links:
- coraza-spoa:coraza-spoa
volumes:
- type: bind
source: ./haproxy/
target: /usr/local/etc/haproxy
- ../build:/build:rw
command:
- /bin/sh
- -c
- haproxy -f /usr/local/etc/haproxy/haproxy.cfg > /build/ftw-haproxy.log
ports:
- 8080:8080
ftw:
depends_on:
- coraza-spoa
- haproxy
build:
context: .
dockerfile: Dockerfile.ftw
network: host
environment:
- FTW_CLOUDMODE
- FTW_INCLUDE
volumes:
- ../build:/build
07070100000016000081A400000000000000000000000168C6803E00000929000000000000000000000000000000000000002300000000coraza-spoa-0.4.0+git3/ftw/ftw.yml---
logfile: '/build/ftw.log'
maxmarkerretries: 10
testoverride:
input:
dest_addr: 'haproxy'
port: 8080
ignore:
# Imported from https://github.com/corazawaf/coraza/blob/main/testing/coreruleset/.ftw.yml
920100-4: 'Invalid uri, Coraza not reached - 404 page not found'
# Failing tests related to upstream issues:
921250-1: 'Expected to match $Version in cookies, failing also in upstream'
921250-2: 'Expected to match $Version in cookies, failing also in upstream'
933120-2: 'To be investigated: match_regex value might be ModSec specific'
# HAProxy specific.
920270-4: 'Rule works, log contains 920270. Test expects status 400 (Apache behaviour)'
920290-1: 'Rule works, log contains 920290. Test expects status 400 (Apache behaviour)'
920430-5: 'Test has expect_error, Go/http, Envoy and HAProxy return 400'
920620-1: 'Rule works, log contains 920620. Test expects 200 (Apache behavour)'
920430-8: 'Unknown HTTP Versions are blocked in HAProxy'
920400-1: 'Limited Body content length because of protocol limitations.'
# TODO investigate failing tests:
922130-1: ''
922130-2: ''
922130-7: ''
942521-17: ''
"920100-10": ''
"920100-14": ''
"920100-16": ''
"920180-1": ''
"920180-3": ''
"920190-2": ''
"920190-3": ''
"920200-1": ''
"920200-2": ''
"920200-4": ''
"920200-5": ''
"920200-6": ''
"920200-8": ''
"920201-1": ''
"920201-2": ''
"920202-1": ''
"920202-2": ''
"920210-2": ''
"920210-3": ''
"920210-4": ''
"920210-6": ''
"920210-7": ''
"920230-1": ''
"920240-1": ''
"920240-5": ''
"920240-6": ''
"920250-1": ''
"920250-2": ''
"920250-3": ''
"920250-4": ''
"920260-1": ''
"920260-3": ''
"920274-1": ''
"920280-1": ''
"920300-1": ''
"920310-1": ''
"920310-4": ''
"920311-1": ''
"920320-1": ''
"920330-1": ''
"920340-1": ''
"920340-2": ''
"920350-1": ''
"920350-3": ''
"920350-4": ''
"920350-5": ''
"920350-6": ''
"920390-1": ''
"920410-1": ''
"934120-23": ''
"934120-24": ''
"934120-25": ''
"934120-26": ''
"934120-39": ''
"942420-1": ''
"942421-1": ''
"942430-1": ''
"942431-1": ''
"942432-1": ''
"942460-1": ''07070100000017000041ED00000000000000000000000268C6803E00000000000000000000000000000000000000000000002300000000coraza-spoa-0.4.0+git3/ftw/haproxy07070100000018000081A400000000000000000000000168C6803E0000037C000000000000000000000000000000000000002E00000000coraza-spoa-0.4.0+git3/ftw/haproxy/coraza.cfg# https://github.com/haproxy/haproxy/blob/master/doc/SPOE.txt
# /usr/local/etc/haproxy/coraza.cfg
[coraza]
spoe-agent coraza-agent
messages coraza-req coraza-res
groups coraza-req coraza-res
option var-prefix coraza
option set-on-error error
timeout hello 2s
timeout idle 2m
timeout processing 500ms
use-backend coraza-spoa
log global
spoe-message coraza-req
args app=str(ftw) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
spoe-message coraza-res
args app=str(ftw) id=var(txn.coraza.id) version=res.ver status=status headers=res.hdrs body=res.body
event on-http-response
spoe-group coraza-req
messages coraza-req
spoe-group coraza-res
messages coraza-res
07070100000019000081A400000000000000000000000168C6803E000007C5000000000000000000000000000000000000002F00000000coraza-spoa-0.4.0+git3/ftw/haproxy/haproxy.cfg# https://docs.haproxy.org/
global
log stdout format raw local0
defaults
log global
option httplog
timeout client 1m
timeout server 1m
timeout connect 10s
frontend default
mode http
bind *:8080
log-format "%ci:%cp\ [%t]\ %ft\ %b/%s\ %Th/%Ti/%TR/%Tq/%Tw/%Tc/%Tr/%Tt\ %ST\ %B\ %CC\ %CS\ %tsc\ %ac/%fc/%bc/%sc/%rc\ %sq/%bq\ %hr\ %hs\ %{+Q}r\ %[var(txn.coraza.id)]\ spoa-error:\ %[var(txn.coraza.error)]\ waf-hit:\ %[var(txn.coraza.fail)] waf-action:\ %[var(txn.coraza.action)] waf-data:\ %[var(txn.coraza.data)]"
# Emulate Apache behavior by only allowing http 1.0, 1.1, 2.0
http-request deny deny_status 400 if !HTTP
http-request deny deny_status 400 if !HTTP_1.0 !HTTP_1.1 !HTTP_2.0
filter spoe engine coraza config /usr/local/etc/haproxy/coraza.cfg
http-request send-spoe-group coraza coraza-req
# Currently haproxy cannot use variables to set the code or deny_status, so this needs to be manually configured here
http-request redirect code 302 location %[var(txn.coraza.data)] if { var(txn.coraza.action) -m str redirect }
http-response redirect code 302 location %[var(txn.coraza.data)] if { var(txn.coraza.action) -m str redirect }
http-request deny deny_status 403 hdr waf-block "request" if { var(txn.coraza.action) -m str deny }
http-response deny deny_status 403 hdr waf-block "response" if { var(txn.coraza.action) -m str deny }
http-request silent-drop if { var(txn.coraza.action) -m str drop }
http-response silent-drop if { var(txn.coraza.action) -m str drop }
# Deny in case of an error, when processing with the Coraza SPOA
http-request deny deny_status 500 if { var(txn.coraza.error) -m int gt 0 }
http-response deny deny_status 500 if { var(txn.coraza.error) -m int gt 0 }
use_backend test
resolvers host_dns
parse-resolv-conf
backend test
mode http
server test backend:8081
backend coraza-spoa
mode tcp
server coraza_spoa coraza-spoa:9000
0707010000001A000081ED00000000000000000000000168C6803E000005C2000000000000000000000000000000000000002400000000coraza-spoa-0.4.0+git3/ftw/tests.sh#!/bin/sh
# Copyright 2025 The OWASP Coraza contributors
# SPDX-License-Identifier: Apache-2.0
cd /workspace
# Revisited from https://github.com/corazawaf/coraza-proxy-wasm/blob/main/ftw/tests.sh
step=1
total_steps=1
max_retries=15 # Seconds for the server reachability timeout
host=${1:-haproxy}
health_url="http://${host}:8080"
log_file='/build/ftw-haproxy.log'
# Testing if the server is up
echo "[$step/$total_steps] Testing application reachability"
status_code="000"
while [[ "$status_code" -eq "000" ]]; do
status_code=$(curl --write-out "%{http_code}" --silent --output /dev/null "$health_url")
sleep 1
echo -ne "[Wait] Waiting for response from $health_url. Timeout: ${max_retries}s \r"
let "max_retries--"
if [[ "$max_retries" -eq 0 ]]; then
echo "[Fail] Timeout waiting for response from $health_url, make sure the server is running."
echo "HAProxy Logs:" && cat "$log_file"
exit 1
fi
done
if [[ "${status_code}" -ne "200" ]]; then
echo "[Fail] Unexpected response with code ${status_code} from ${health_url}, expected 200."
echo "HAProxy Logs:" && cat "$log_file"
exit 1
fi
echo -e "\n[Ok] Got status code $status_code, expected 200. Ready to start."
FTW_CLOUDMODE=${FTW_CLOUDMODE:-false}
FTW_INCLUDE=$([ "${FTW_INCLUDE}" == "" ] && echo "" || echo "-i ${FTW_INCLUDE}")
/ftw run -d coreruleset/tests/regression/tests --config ftw.yml --read-timeout=10s --max-marker-retries=50 --cloud=$FTW_CLOUDMODE $FTW_INCLUDE || exit 1
0707010000001B000081A400000000000000000000000168C6803E00000684000000000000000000000000000000000000001E00000000coraza-spoa-0.4.0+git3/go.modmodule github.com/corazawaf/coraza-spoa
go 1.23.0
require (
github.com/corazawaf/coraza-coreruleset/v4 v4.17.1
github.com/corazawaf/coraza/v3 v3.3.3
github.com/dropmorepackets/haproxy-go v0.0.7
github.com/fsnotify/fsnotify v1.9.0
github.com/jcchavezs/mergefs v0.1.0
github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae
github.com/mccutchen/go-httpbin/v2 v2.18.3
github.com/prometheus/client_golang v1.23.2
github.com/rs/zerolog v1.34.0
gopkg.in/yaml.v3 v3.0.1
istio.io/istio v0.0.0-20240218163812-d80ef7b19049
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/corazawaf/libinjection-go v0.2.2 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/valllabh/ocsf-schema-golang v1.0.3 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.35.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
rsc.io/binaryregexp v0.2.0 // indirect
)
0707010000001C000081A400000000000000000000000168C6803E000037D7000000000000000000000000000000000000001E00000000coraza-spoa-0.4.0+git3/go.sumgithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/corazawaf/coraza-coreruleset v0.0.0-20240226094324-415b1017abdc h1:OlJhrgI3I+FLUCTI3JJW8MoqyM78WbqJjecqMnqG+wc=
github.com/corazawaf/coraza-coreruleset v0.0.0-20240226094324-415b1017abdc/go.mod h1:7rsocqNDkTCira5T0M7buoKR2ehh7YZiPkzxRuAgvVU=
github.com/corazawaf/coraza-coreruleset/v4 v4.13.0 h1:02hXtxh0tCLR2LCKU/cmZxxjCgjgC0P1uM8DjJEreMY=
github.com/corazawaf/coraza-coreruleset/v4 v4.13.0/go.mod h1:yeZPZUM23HVL0jMAzLfKF3M7XjCQBXDrvcQT/gkjwhg=
github.com/corazawaf/coraza-coreruleset/v4 v4.14.0 h1:bblMqFRGQyREP81dsJ7RRiHua0LW7apuIrDwKy8qFHg=
github.com/corazawaf/coraza-coreruleset/v4 v4.14.0/go.mod h1:yeZPZUM23HVL0jMAzLfKF3M7XjCQBXDrvcQT/gkjwhg=
github.com/corazawaf/coraza-coreruleset/v4 v4.15.0 h1:rfdp5NK7ehB8+f0wcCaEAmPJ3h/7pD7KT085xyNVKIo=
github.com/corazawaf/coraza-coreruleset/v4 v4.15.0/go.mod h1:yeZPZUM23HVL0jMAzLfKF3M7XjCQBXDrvcQT/gkjwhg=
github.com/corazawaf/coraza-coreruleset/v4 v4.16.0 h1:xbC785u2JYTkoZpYDchW3NOys8sKdFBmh2JTpva1Czc=
github.com/corazawaf/coraza-coreruleset/v4 v4.16.0/go.mod h1:yeZPZUM23HVL0jMAzLfKF3M7XjCQBXDrvcQT/gkjwhg=
github.com/corazawaf/coraza-coreruleset/v4 v4.17.1 h1:p/ukmri8hNJcZI5PA1OY54sw1eVndRH7gwwMWMhtEgw=
github.com/corazawaf/coraza-coreruleset/v4 v4.17.1/go.mod h1:yeZPZUM23HVL0jMAzLfKF3M7XjCQBXDrvcQT/gkjwhg=
github.com/corazawaf/coraza/v3 v3.3.3 h1:kqjStHAgWqwP5dh7n0vhTOF0a3t+VikNS/EaMiG0Fhk=
github.com/corazawaf/coraza/v3 v3.3.3/go.mod h1:xSaXWOhFMSbrV8qOOfBKAyw3aOqfwaSaOy5BgSF8XlA=
github.com/corazawaf/libinjection-go v0.2.2 h1:Chzodvb6+NXh6wew5/yhD0Ggioif9ACrQGR4qjTCs1g=
github.com/corazawaf/libinjection-go v0.2.2/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dropmorepackets/haproxy-go v0.0.5 h1:a6aT2UrdS9MvV60ZLZnXFgi19jxRvVg/lJFQCiFYDFA=
github.com/dropmorepackets/haproxy-go v0.0.5/go.mod h1:4a2AmmVjvg2zPNdizGZrMN8ZSUpj90U43VlcdbOIBnU=
github.com/dropmorepackets/haproxy-go v0.0.6 h1:0u0u4MLS+mbIrYCQrIkHq8PQvt6ePJgF6ogTIFZQzx8=
github.com/dropmorepackets/haproxy-go v0.0.6/go.mod h1:4a2AmmVjvg2zPNdizGZrMN8ZSUpj90U43VlcdbOIBnU=
github.com/dropmorepackets/haproxy-go v0.0.7 h1:atXkB0MSRBZrAgpq+Vj/E4KysQ4CiI0O5QGUr+HvfTw=
github.com/dropmorepackets/haproxy-go v0.0.7/go.mod h1:4a2AmmVjvg2zPNdizGZrMN8ZSUpj90U43VlcdbOIBnU=
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/jcchavezs/mergefs v0.1.0 h1:7oteO7Ocl/fnfFMkoVLJxTveCjrsd//UB0j89xmnpec=
github.com/jcchavezs/mergefs v0.1.0/go.mod h1:eRLTrsA+vFwQZ48hj8p8gki/5v9C2bFtHH5Mnn4bcGk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516 h1:aAO0L0ulox6m/CLRYvJff+jWXYYCKGpEm3os7dM/Z+M=
github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae h1:yyMUG1VUd6IjV5jonMKpLXgwm9AzkfRsYisdCXc5OVI=
github.com/magefile/mage v1.15.1-0.20250615140142-78acbaf2e3ae/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mccutchen/go-httpbin/v2 v2.18.1 h1:Zsi6gLCUS/kqZf36BicIbZsO8o0VyO2nSaOv/J8XKlQ=
github.com/mccutchen/go-httpbin/v2 v2.18.1/go.mod h1:GBy5I7XwZ4ZLhT3hcq39I4ikwN9x4QUt6EAxNiR8Jus=
github.com/mccutchen/go-httpbin/v2 v2.18.2 h1:UU5rd5ohZFX7ZyuTwINL4EBnParq0nM2JoJIVjA6hGQ=
github.com/mccutchen/go-httpbin/v2 v2.18.2/go.mod h1:GBy5I7XwZ4ZLhT3hcq39I4ikwN9x4QUt6EAxNiR8Jus=
github.com/mccutchen/go-httpbin/v2 v2.18.3 h1:DyckIScjHLJtmlSju+rgjqqI1nL8AdMZHsLSljlbnMU=
github.com/mccutchen/go-httpbin/v2 v2.18.3/go.mod h1:GBy5I7XwZ4ZLhT3hcq39I4ikwN9x4QUt6EAxNiR8Jus=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4 h1:1Kw2vDBXmjop+LclnzCb/fFy+sgb3gYARwfmoUcQe6o=
github.com/petar-dambovaliev/aho-corasick v0.0.0-20240411101913-e07a1f0e8eb4/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y=
github.com/prometheus/client_golang v1.23.1/go.mod h1:br8j//v2eg2K5Vvna5klK8Ku5pcU5r4ll73v6ik5dIQ=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY=
github.com/prometheus/common v0.66.0/go.mod h1:Ux6NtV1B4LatamKE63tJBntoxD++xmtI/lK0VtEplN4=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/valllabh/ocsf-schema-golang v1.0.3 h1:eR8k/3jP/OOqB8LRCtdJ4U+vlgd/gk5y3KMXoodrsrw=
github.com/valllabh/ocsf-schema-golang v1.0.3/go.mod h1:sZ3as9xqm1SSK5feFWIR2CuGeGRhsM7TR1MbpBctzPk=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
istio.io/istio v0.0.0-20240218163812-d80ef7b19049 h1:jR4INLKnkLNgQRNMBjkAt1ctPnuTq+vQ9wlZSOtR1+o=
istio.io/istio v0.0.0-20240218163812-d80ef7b19049/go.mod h1:5ATT2TaGbT/L1SwCYvs2ArNeLxHkPKwhvT7r3TPMu6M=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
0707010000001D000041ED00000000000000000000000268C6803E00000000000000000000000000000000000000000000002000000000coraza-spoa-0.4.0+git3/internal0707010000001E000081A400000000000000000000000168C6803E00000C7A000000000000000000000000000000000000002900000000coraza-spoa-0.4.0+git3/internal/agent.gopackage internal
import (
"context"
"errors"
"net"
"sync"
"github.com/dropmorepackets/haproxy-go/pkg/encoding"
"github.com/dropmorepackets/haproxy-go/spop"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog"
)
type Agent struct {
Context context.Context
DefaultApplication *Application
Applications map[string]*Application
Logger zerolog.Logger
mtx sync.RWMutex
}
func (a *Agent) Serve(l net.Listener) error {
agent := spop.Agent{
Handler: a,
BaseContext: a.Context,
}
return agent.Serve(l)
}
func (a *Agent) ReplaceApplications(newApps map[string]*Application) {
a.mtx.Lock()
a.Applications = newApps
a.mtx.Unlock()
}
func (a *Agent) HandleSPOE(ctx context.Context, writer *encoding.ActionWriter, message *encoding.Message) {
timer := prometheus.NewTimer(handleSPOEDuration)
defer timer.ObserveDuration()
const (
messageCorazaRequest = "coraza-req"
messageCorazaResponse = "coraza-res"
)
var messageHandler func(*Application, context.Context, *encoding.ActionWriter, *encoding.Message) error
switch name := string(message.NameBytes()); name {
case messageCorazaRequest:
messageHandler = (*Application).HandleRequest
case messageCorazaResponse:
messageHandler = (*Application).HandleResponse
default:
a.Logger.Debug().Str("message", name).Msg("unknown spoe message")
return
}
k := encoding.AcquireKVEntry()
defer encoding.ReleaseKVEntry(k)
if !message.KV.Next(k) {
a.Logger.Panic().Msg("failed reading kv entry")
return
}
appName := string(k.ValueBytes())
if !k.NameEquals("app") {
// Without knowing the app, we cannot continue. We could fall back to a default application,
// but all following code would have to support that as we now already read one of the kv entries.
a.Logger.Panic().Str("expected", "app").Str("got", string(k.NameBytes())).Msg("unexpected kv entry")
return
}
a.mtx.RLock()
app := a.Applications[appName]
a.mtx.RUnlock()
if app == nil && a.DefaultApplication != nil {
// If we cannot resolve the app but the default app is configured,
// we use the latter to process the request.
app = a.DefaultApplication
a.Logger.Debug().Str("app", appName).Msg("app not found, using default app")
}
if app == nil {
// If we cannot resolve the app, we fail as this is an invalid configuration.
a.Logger.Panic().Str("app", appName).Msg("app not found")
return
}
err := messageHandler(app, ctx, writer, message)
if err == nil {
return
}
var interruption ErrInterrupted
if err != nil && errors.As(err, &interruption) {
_ = writer.SetInt64(encoding.VarScopeTransaction, "status", int64(interruption.Interruption.Status))
_ = writer.SetString(encoding.VarScopeTransaction, "action", interruption.Interruption.Action)
_ = writer.SetString(encoding.VarScopeTransaction, "data", interruption.Interruption.Data)
_ = writer.SetInt64(encoding.VarScopeTransaction, "ruleid", int64(interruption.Interruption.RuleID))
a.Logger.Debug().Err(err).Msg("sending interruption")
return
}
// If the error is not an ErrInterrupted, we panic to let the spop stream fail.
a.Logger.Panic().Err(err).Msg("Error handling request")
}
0707010000001F000081A400000000000000000000000168C6803E00002EBE000000000000000000000000000000000000002F00000000coraza-spoa-0.4.0+git3/internal/application.gopackage internal
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"math/rand"
"net/netip"
"strings"
"sync"
"time"
coreruleset "github.com/corazawaf/coraza-coreruleset/v4"
"github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/types"
"github.com/dropmorepackets/haproxy-go/pkg/encoding"
"github.com/jcchavezs/mergefs"
"github.com/jcchavezs/mergefs/io"
"github.com/rs/zerolog"
"istio.io/istio/pkg/cache"
)
type AppConfig struct {
Directives string
ResponseCheck bool
Logger zerolog.Logger
TransactionTTL time.Duration
LogFormat string
}
type Application struct {
waf coraza.WAF
cache cache.ExpiringCache
AppConfig
}
type transaction struct {
tx types.Transaction
m sync.Mutex
}
type applicationRequest struct {
SrcIp netip.Addr
SrcPort int64
DstIp netip.Addr
DstPort int64
Method string
ID string
Path []byte
Query []byte
Version string
Headers []byte
Body []byte
}
func (a *Application) HandleRequest(ctx context.Context, writer *encoding.ActionWriter, message *encoding.Message) (err error) {
k := encoding.AcquireKVEntry()
// run defer via anonymous function to not directly evaluate the arguments.
defer func() {
encoding.ReleaseKVEntry(k)
}()
var req applicationRequest
for message.KV.Next(k) {
switch name := string(k.NameBytes()); name {
case "src-ip":
req.SrcIp = k.ValueAddr()
case "src-port":
req.SrcPort = k.ValueInt()
case "dst-ip":
req.DstIp = k.ValueAddr()
case "dst-port":
req.DstPort = k.ValueInt()
case "method":
req.Method = string(k.ValueBytes())
case "path":
// make a copy of the pointer and add a defer in case there is another entry
currK := k
// run defer via anonymous function to not directly evaluate the arguments.
defer func() {
encoding.ReleaseKVEntry(currK)
}()
req.Path = currK.ValueBytes()
// acquire a new kv entry to continue reading other message values.
k = encoding.AcquireKVEntry()
case "query":
// make a copy of the pointer and add a defer in case there is another entry
currK := k
// run defer via anonymous function to not directly evaluate the arguments.
defer func() {
encoding.ReleaseKVEntry(currK)
}()
req.Query = currK.ValueBytes()
// acquire a new kv entry to continue reading other message values.
k = encoding.AcquireKVEntry()
case "version":
req.Version = string(k.ValueBytes())
case "headers":
// make a copy of the pointer and add a defer in case there is another entry
currK := k
// run defer via anonymous function to not directly evaluate the arguments.
defer func() {
encoding.ReleaseKVEntry(currK)
}()
req.Headers = currK.ValueBytes()
// acquire a new kv entry to continue reading other message values.
k = encoding.AcquireKVEntry()
case "body":
// make a copy of the pointer and add a defer in case there is another entry
currK := k
// run defer via anonymous function to not directly evaluate the arguments.
defer func() {
encoding.ReleaseKVEntry(currK)
}()
req.Body = currK.ValueBytes()
// acquire a new kv entry to continue reading other message values.
k = encoding.AcquireKVEntry()
case "id":
req.ID = string(k.ValueBytes())
default:
a.Logger.Debug().Str("name", name).Msg("unknown kv entry")
}
}
// Check if we have received an id from haproxy
if len(req.ID) == 0 {
const idLength = 16
var sb strings.Builder
sb.Grow(idLength)
for i := 0; i < idLength; i++ {
sb.WriteRune(rune('A' + rand.Intn(26)))
}
req.ID = sb.String()
}
tx := a.waf.NewTransactionWithID(req.ID)
defer func() {
if err == nil && a.ResponseCheck {
a.cache.SetWithExpiration(tx.ID(), &transaction{tx: tx}, a.TransactionTTL)
return
}
tx.ProcessLogging()
if err := tx.Close(); err != nil {
a.Logger.Error().Str("tx", tx.ID()).Err(err).Msg("failed to close transaction")
}
}()
if err := writer.SetString(encoding.VarScopeTransaction, "id", tx.ID()); err != nil {
return err
}
if tx.IsRuleEngineOff() {
a.Logger.Warn().Msg("Rule engine is Off, Coraza is not going to process any rule")
return nil
}
tx.ProcessConnection(req.SrcIp.String(), int(req.SrcPort), req.DstIp.String(), int(req.DstPort))
{
url := strings.Builder{}
url.Write(req.Path)
if req.Query != nil {
url.WriteString("?")
url.Write(req.Query)
}
tx.ProcessURI(url.String(), req.Method, "HTTP/"+req.Version)
}
if err := readHeaders(req.Headers, tx.AddRequestHeader, tx.SetServerName); err != nil {
return fmt.Errorf("reading headers: %v", err)
}
if it := tx.ProcessRequestHeaders(); it != nil {
return ErrInterrupted{it}
}
switch it, _, err := tx.WriteRequestBody(req.Body); {
case err != nil:
return err
case it != nil:
return ErrInterrupted{it}
}
switch it, err := tx.ProcessRequestBody(); {
case err != nil:
return err
case it != nil:
return ErrInterrupted{it}
}
return nil
}
func readHeaders(headers []byte, hdrCallback func(key string, value string), hostCallback func(value string)) error {
s := bufio.NewScanner(bytes.NewReader(headers))
for s.Scan() {
line := bytes.TrimSpace(s.Bytes())
if len(line) == 0 {
continue
}
kv := bytes.SplitN(line, []byte(":"), 2)
if len(kv) != 2 {
return fmt.Errorf("invalid header: %q", s.Text())
}
key, value := bytes.TrimSpace(kv[0]), bytes.TrimSpace(kv[1])
if hostCallback != nil && string(key) == "host" {
hostCallback(string(value))
}
hdrCallback(string(key), string(value))
}
return nil
}
type applicationResponse struct {
ID string
Version string
Status int64
Headers []byte
Body []byte
}
func (a *Application) HandleResponse(ctx context.Context, writer *encoding.ActionWriter, message *encoding.Message) (err error) {
if !a.ResponseCheck {
return fmt.Errorf("got response but response check is disabled")
}
k := encoding.AcquireKVEntry()
// run defer via anonymous function to not directly evaluate the arguments.
defer func() {
encoding.ReleaseKVEntry(k)
}()
var res applicationResponse
for message.KV.Next(k) {
switch name := string(k.NameBytes()); name {
case "id":
res.ID = string(k.ValueBytes())
case "version":
res.Version = string(k.ValueBytes())
case "status":
res.Status = k.ValueInt()
case "headers":
// make a copy of the pointer and add a defer in case there is another entry
currK := k
// run defer via anonymous function to not directly evaluate the arguments.
defer func() {
encoding.ReleaseKVEntry(currK)
}()
res.Headers = currK.ValueBytes()
// acquire a new kv entry to continue reading other message values.
k = encoding.AcquireKVEntry()
case "body":
// make a copy of the pointer and add a defer in case there is another entry
currK := k
// run defer via anonymous function to not directly evaluate the arguments.
defer func() {
encoding.ReleaseKVEntry(currK)
}()
res.Body = currK.ValueBytes()
// acquire a new kv entry to continue reading other message values.
k = encoding.AcquireKVEntry()
default:
a.Logger.Debug().Str("name", name).Msg("unknown kv entry")
}
}
if res.ID == "" {
return fmt.Errorf("response id is empty")
}
cv, ok := a.cache.Get(res.ID)
if !ok {
return fmt.Errorf("transaction not found: %s", res.ID)
}
a.cache.Remove(res.ID)
t := cv.(*transaction)
if !t.m.TryLock() {
return fmt.Errorf("transaction is already being deleted: %s", res.ID)
}
tx := t.tx
defer func() {
tx.ProcessLogging()
if err := tx.Close(); err != nil {
a.Logger.Error().Str("tx", tx.ID()).Err(err).Msg("failed to close transaction")
}
}()
if tx.IsRuleEngineOff() {
goto exit
}
if err := readHeaders(res.Headers, tx.AddResponseHeader, nil); err != nil {
return fmt.Errorf("reading headers: %v", err)
}
if it := tx.ProcessResponseHeaders(int(res.Status), "HTTP/"+res.Version); it != nil {
return ErrInterrupted{it}
}
switch it, _, err := tx.WriteResponseBody(res.Body); {
case err != nil:
return err
case it != nil:
return ErrInterrupted{it}
}
switch it, err := tx.ProcessResponseBody(); {
case err != nil:
return err
case it != nil:
return ErrInterrupted{it}
}
exit:
return nil
}
func (a AppConfig) NewApplication() (*Application, error) {
app := Application{
AppConfig: a,
}
config := coraza.NewWAFConfig().
WithDirectives(a.Directives).
WithErrorCallback(app.logCallback).
WithRootFS(mergefs.Merge(coreruleset.FS, io.OSFS))
waf, err := coraza.NewWAF(config)
if err != nil {
return nil, err
}
app.waf = waf
const defaultExpire = time.Second * 10
const defaultEvictionInterval = time.Second * 1
app.cache = cache.NewTTLWithCallback(defaultExpire, defaultEvictionInterval, func(key, value any) {
// everytime a transaction runs into a timeout it gets closed.
t := value.(*transaction)
if !t.m.TryLock() {
// We lost a race and the transaction is already somewhere in use.
a.Logger.Info().Str("tx", t.tx.ID()).Msg("eviction called on currently used transaction")
return
}
// Process Logging won't do anything if TX was already logged.
t.tx.ProcessLogging()
if err := t.tx.Close(); err != nil {
a.Logger.Error().Err(err).Str("tx", t.tx.ID()).Msg("error closing transaction")
}
})
return &app, nil
}
func matchedRuleErrorJson(mr types.MatchedRule) []byte {
type errorLog struct {
Client string `json:"client"`
File string `json:"file"`
Line int `json:"line"`
RuleID int `json:"rule_id"`
Revision string `json:"revision"`
Msg string `json:"msg"`
Data string `json:"data"`
Severity string `json:"severity"`
SeverityID int `json:"severity_id"`
Version string `json:"version"`
Maturity int `json:"maturity"`
Accuracy int `json:"accuracy"`
Tags []string `json:"tags"`
Server string `json:"server"`
URI string `json:"uri"`
UniqueID string `json:"unique_id"`
Disruptive bool `json:"disruptive"`
PhaseID int `json:"phase_id"`
Phase string `json:"phase"`
}
r := mr.Rule()
j, _ := json.Marshal(errorLog{
File: r.File(),
Line: r.Line(),
RuleID: r.ID(),
Revision: r.Revision(),
Severity: r.Severity().String(),
SeverityID: r.Severity().Int(),
Version: r.Version(),
Maturity: r.Maturity(),
Accuracy: r.Accuracy(),
Tags: r.Tags(),
Msg: mr.Message(),
Data: mr.Data(),
Client: mr.ClientIPAddress(),
Server: mr.ServerIPAddress(),
Disruptive: mr.Disruptive(),
URI: mr.URI(),
UniqueID: mr.TransactionID(),
PhaseID: int(r.Phase()),
Phase: phaseToString(r.Phase()),
})
return j
}
func phaseToString(phase types.RulePhase) string {
switch phase {
case types.PhaseRequestHeaders:
return "request-headers"
case types.PhaseRequestBody:
return "request-body"
case types.PhaseResponseHeaders:
return "response-headers"
case types.PhaseResponseBody:
return "response-body"
case types.PhaseLogging:
return "logging"
default:
return "unknown"
}
}
func (a *Application) logCallback(mr types.MatchedRule) {
var l *zerolog.Event
switch mr.Rule().Severity() {
case types.RuleSeverityWarning:
l = a.Logger.Warn()
case types.RuleSeverityNotice,
types.RuleSeverityInfo:
l = a.Logger.Info()
case types.RuleSeverityDebug:
l = a.Logger.Debug()
default:
l = a.Logger.Error()
}
switch a.LogFormat {
case "json":
l.RawJSON("match", matchedRuleErrorJson(mr)).Send()
default:
l.Msg(mr.ErrorLog())
}
}
type ErrInterrupted struct {
Interruption *types.Interruption
}
func (e ErrInterrupted) Error() string {
return fmt.Sprintf("interrupted with status %d and action %s", e.Interruption.Status, e.Interruption.Action)
}
func (e ErrInterrupted) Is(target error) bool {
t, ok := target.(*ErrInterrupted)
if !ok {
return false
}
return e.Interruption == t.Interruption
}
07070100000020000081A400000000000000000000000168C6803E00001062000000000000000000000000000000000000002C00000000coraza-spoa-0.4.0+git3/internal/e2e_test.go//go:build e2e
package internal
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"sync"
"testing"
"time"
"github.com/corazawaf/coraza/v3/http/e2e"
"github.com/dropmorepackets/haproxy-go/pkg/testutil"
"github.com/mccutchen/go-httpbin/v2/httpbin"
"github.com/rs/zerolog"
)
func TestE2E(t *testing.T) {
t.Run("coraza e2e suite", func(t *testing.T) {
config, bin, _ := runCoraza(t)
err := e2e.Run(e2e.Config{
NulledBody: false,
ProxiedEntrypoint: "http://127.0.0.1:" + config.FrontendPort,
HttpbinEntrypoint: bin,
})
if err != nil {
t.Fatalf("e2e tests failed: %v", err)
}
})
t.Run("high request rate", func(t *testing.T) {
config, _, _ := runCoraza(t)
if os.Getenv("CI") != "" {
t.Skip("CI is too slow for this test.")
}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
req, _ := http.NewRequest("GET", "http://127.0.0.1:"+config.FrontendPort+"/get", http.NoBody)
req.Header.Set("coraza-e2e", "ok")
resp, _ := http.DefaultClient.Do(req)
if resp.StatusCode != http.StatusOK {
t.Error(resp.Status)
}
}
}()
}
wg.Wait()
})
}
func runCoraza(tb testing.TB) (testutil.HAProxyConfig, string, string) {
s := httptest.NewServer(httpbin.New())
tb.Cleanup(s.Close)
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
appCfg := AppConfig{
Directives: e2e.Directives,
ResponseCheck: true,
Logger: logger,
TransactionTTL: 10 * time.Second,
}
application, err := appCfg.NewApplication()
if err != nil {
tb.Fatal(err)
}
a := Agent{
Context: context.Background(),
DefaultApplication: application,
Applications: map[string]*Application{
"default": application,
},
Logger: logger,
}
// create the listener synchronously to prevent a race
l := testutil.TCPListener(tb)
// ignore errors as the listener will be closed by t.Cleanup
go a.Serve(l)
cfg := testutil.HAProxyConfig{
EngineAddr: l.Addr().String(),
FrontendPort: fmt.Sprintf("%d", testutil.TCPPort(tb)),
CustomFrontendConfig: `
# Currently haproxy cannot use variables to set the code or deny_status, so this needs to be manually configured here
http-request redirect code 302 location %[var(txn.e2e.data)] if { var(txn.e2e.action) -m str redirect }
http-response redirect code 302 location %[var(txn.e2e.data)] if { var(txn.e2e.action) -m str redirect }
acl is_deny var(txn.e2e.action) -m str deny
acl status_424 var(txn.e2e.status) -m int 424
# Special check for e2e tests as they validate the config.
http-request deny deny_status 424 hdr waf-block "request" if is_deny status_424
http-response deny deny_status 424 hdr waf-block "response" if is_deny status_424
http-request deny deny_status 403 hdr waf-block "request" if is_deny
http-response deny deny_status 403 hdr waf-block "response" if is_deny
http-request silent-drop if { var(txn.e2e.action) -m str drop }
http-response silent-drop if { var(txn.e2e.action) -m str drop }
# Deny in case of an error, when processing with the Coraza SPOA
http-request deny deny_status 504 if { var(txn.e2e.error) -m int gt 0 }
http-response deny deny_status 504 if { var(txn.e2e.error) -m int gt 0 }
`,
EngineConfig: `
[e2e]
spoe-agent e2e
messages coraza-req coraza-res
option var-prefix e2e
option set-on-error error
timeout hello 2s
timeout idle 2m
timeout processing 500ms
use-backend e2e-spoa
log global
spoe-message coraza-req
args app=str(default) src-ip=src src-port=src_port dst-ip=dst dst-port=dst_port method=method path=path query=query version=req.ver headers=req.hdrs body=req.body
event on-frontend-http-request
spoe-message coraza-res
args app=str(default) id=var(txn.e2e.id) version=res.ver status=status headers=res.hdrs body=res.body
event on-http-response
`,
BackendConfig: fmt.Sprintf(`
mode http
server httpbin %s
`, s.Listener.Addr().String()),
}
frontendSocket := cfg.Run(tb)
return cfg, s.URL, frontendSocket
}
07070100000021000081A400000000000000000000000168C6803E0000016A000000000000000000000000000000000000002B00000000coraza-spoa-0.4.0+git3/internal/metrics.gopackage internal
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
handleSPOEDuration = promauto.NewHistogram(
prometheus.HistogramOpts{
Name: "coraza_handle_spoe_duration_seconds",
Help: "Duration of Coraza SPOE handling",
Buckets: prometheus.DefBuckets,
},
)
)
07070100000022000081A400000000000000000000000168C6803E00000153000000000000000000000000000000000000001F00000000coraza-spoa-0.4.0+git3/mage.go// Copyright 2022 The OWASP Coraza contributors
// SPDX-License-Identifier: Apache-2.0
//go:build ignore
// +build ignore
// Entrypoint to mage for running without needing to install the command.
// https://magefile.org/zeroinstall/
package main
import (
"os"
"github.com/magefile/mage/mage"
)
func main() {
os.Exit(mage.Main())
}
07070100000023000081A400000000000000000000000168C6803E00001290000000000000000000000000000000000000002300000000coraza-spoa-0.4.0+git3/magefile.go// Copyright 2024 The OWASP Coraza contributors
// SPDX-License-Identifier: Apache-2.0
//go:build mage
// +build mage
package main
import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
var addLicenseVersion = "v1.1.1" // https://github.com/google/addlicense/releases
var gosImportsVer = "v0.3.7" // https://github.com/rinchsan/gosimports/releases
var golangCILintVer = "v1.62.0" // https://github.com/golangci/golangci-lint/releases
var errNoGitDir = errors.New("no .git directory found")
var errUpdateGeneratedFiles = errors.New("generated files need to be updated")
// Format formats code in this repository.
func Format() error {
if err := sh.RunV("go", "generate", "./..."); err != nil {
return err
}
if err := sh.RunV("go", "mod", "tidy"); err != nil {
return err
}
if err := sh.RunV("go", "work", "sync"); err != nil {
return err
}
// addlicense strangely logs skipped files to stderr despite not being erroneous, so use the long sh.Exec form to
// discard stderr too.
if _, err := sh.Exec(map[string]string{}, io.Discard, io.Discard, "go", "run", fmt.Sprintf("github.com/google/addlicense@%s", addLicenseVersion),
"-c", "The OWASP Coraza contributors",
"-s=only",
"-ignore", "**/*.yml",
"-ignore", "**/*.yaml",
"-ignore", "examples/**", "."); err != nil {
return err
}
return sh.RunV("go", "run", fmt.Sprintf("github.com/rinchsan/gosimports/cmd/gosimports@%s", gosImportsVer),
"-w",
"-local",
"github.com/corazawaf/coraza-spoa",
".")
}
func Build() error {
arch := os.Getenv("ARCH")
if arch == "" {
arch = runtime.GOARCH
}
gitVersion, _ := sh.Output("git", "describe", "--tags", "--always", "--dirty")
ldflags := fmt.Sprintf("-X 'main.version=%s'", gitVersion)
if err := sh.RunWith(map[string]string{
"GOARCH": arch,
}, "go", "build", "-ldflags="+ldflags, "-o", "build/coraza-spoa"); err != nil {
return err
}
return nil
}
// Lint verifies code quality.
func Lint() error {
if err := sh.RunV("go", "generate", "./..."); err != nil {
return err
}
if sh.Run("git", "diff", "--exit-code", "--", "'*.gen.go'") != nil {
return errUpdateGeneratedFiles
}
if err := sh.RunV("go", "run", fmt.Sprintf("github.com/golangci/golangci-lint/cmd/golangci-lint@%s", golangCILintVer), "run"); err != nil {
return err
}
if err := filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
return nil
}
if _, err := os.Stat(filepath.Join(path, "go.mod")); err == nil {
cmd := exec.Command("go", "mod", "tidy")
cmd.Dir = path
out, err := cmd.CombinedOutput()
fmt.Printf(string(out))
if err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
return nil
}
// Test runs all tests.
func Test() error {
if err := sh.RunV("go", "test", "./..."); err != nil {
return err
}
// we specify the package to get streaming test output
if err := sh.RunV("go", "test", "-race", "-v", "-tags=e2e", "./internal"); err != nil {
return err
}
return nil
}
// Coverage runs tests with coverage and race detector enabled.
func Coverage() error {
if err := os.MkdirAll("build", 0755); err != nil {
return err
}
if err := sh.RunV("go", "test", "-race", "-coverprofile=build/coverage.txt", "-covermode=atomic", "-coverpkg=./...", "./..."); err != nil {
return err
}
return sh.RunV("go", "tool", "cover", "-html=build/coverage.txt", "-o", "build/coverage.html")
}
// Doc runs godoc, access at http://localhost:6060
func Doc() error {
return sh.RunV("go", "run", "golang.org/x/tools/cmd/godoc@latest", "-http=:6060")
}
// Precommit installs a git hook to run check when committing
func Precommit() error {
if _, err := os.Stat(filepath.Join(".git", "hooks")); os.IsNotExist(err) {
return errNoGitDir
}
f, err := os.ReadFile(".pre-commit.hook")
if err != nil {
return err
}
return os.WriteFile(filepath.Join(".git", "hooks", "pre-commit"), f, 0755)
}
// Check runs lint and tests.
func Check() {
mg.SerialDeps(Lint, Test)
}
// Ftw runs CRS regressions tests. Requires docker.
func Ftw() error {
env := map[string]string{
"FTW_CLOUDMODE": os.Getenv("FTW_CLOUDMODE"),
"FTW_INCLUDE": os.Getenv("FTW_INCLUDE"),
"FTW_HAPROXY_VERSION": os.Getenv("FTW_HAPROXY_VERSION"),
}
if err := sh.RunWithV(env, "docker", "compose", "--file", "ftw/docker-compose.yml", "build", "--pull", "--no-cache"); err != nil {
return err
}
defer func() {
_ = sh.RunWithV(env, "docker", "compose", "--file", "ftw/docker-compose.yml", "down", "-v")
}()
return sh.RunWithV(env, "docker", "compose", "--file", "ftw/docker-compose.yml", "run", "--rm", "ftw")
}
07070100000024000081A400000000000000000000000168C6803E000010DA000000000000000000000000000000000000001F00000000coraza-spoa-0.4.0+git3/main.go// Copyright The OWASP Coraza contributors
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"flag"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"runtime"
"runtime/debug"
"runtime/pprof"
"syscall"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog"
"github.com/corazawaf/coraza-spoa/internal"
)
var (
version = "dev"
)
var (
configPath string
validateConfig bool
autoReload bool
cpuProfile string
memProfile string
metricsAddr string
showVersion bool
globalLogger = zerolog.New(os.Stderr).With().Timestamp().Logger()
)
func main() {
flag.StringVar(&configPath, "config", "", "configuration file")
flag.BoolVar(&validateConfig, "validate", false, "validate configuration file and exit")
flag.BoolVar(&autoReload, "autoreload", false, "reload configuration file on k8s configmap update")
flag.StringVar(&cpuProfile, "cpuprofile", "", "write cpu profile to `file`")
flag.StringVar(&memProfile, "memprofile", "", "write memory profile to `file`")
flag.StringVar(&metricsAddr, "metrics-addr", "", "ip:port bind for prometheus metrics")
flag.BoolVar(&showVersion, "version", false, "show version and exit")
flag.Parse()
if showVersion {
fmt.Printf("version\t%s\n", version)
if bi, ok := debug.ReadBuildInfo(); ok {
fmt.Printf("%s", bi.String())
}
return
}
if configPath == "" {
globalLogger.Fatal().Msg("Configuration file is not set")
}
if cpuProfile != "" {
f, err := os.Create(cpuProfile)
if err != nil {
globalLogger.Fatal().Err(err).Msg("Could not create CPU profile")
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
globalLogger.Fatal().Err(err).Msg("Could not start CPU profile")
}
defer pprof.StopCPUProfile()
}
cfg, err := readConfig()
if err != nil {
globalLogger.Fatal().Err(err).Msg("Failed loading config")
}
logger, err := cfg.Log.newLogger()
if err != nil {
globalLogger.Fatal().Err(err).Msg("Failed creating global logger")
}
globalLogger = logger
apps, err := cfg.newApplications()
if err != nil {
globalLogger.Fatal().Err(err).Msg("Failed creating applications")
}
if validateConfig {
globalLogger.Info().Msg("Configuration file is valid")
return
}
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
network, address := cfg.networkAddressFromBind()
l, err := (&net.ListenConfig{}).Listen(ctx, network, address)
if err != nil {
globalLogger.Fatal().Err(err).Msg("Failed opening socket")
}
a := &internal.Agent{
Context: ctx,
DefaultApplication: apps[cfg.DefaultApplication],
Applications: apps,
Logger: globalLogger,
}
go func() {
defer cancelFunc()
globalLogger.Info().Msg("Starting coraza-spoa")
if err := a.Serve(l); err != nil {
globalLogger.Fatal().Err(err).Msg("Listener closed")
}
}()
if metricsAddr != "" {
go func() {
http.Handle("/metrics", promhttp.Handler())
if err := http.ListenAndServe(metricsAddr, nil); err != nil {
globalLogger.Error().Err(err).Msg("Metrics server failed")
}
}()
}
if autoReload {
go func() {
if err := cfg.watchConfig(a); err != nil {
globalLogger.Fatal().Err(err).Msg("Config watcher failed")
}
}()
}
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGINT)
outer:
for {
sig := <-sigCh
switch sig {
case syscall.SIGTERM:
globalLogger.Info().Msg("Received SIGTERM, shutting down...")
// this return will run cancel() and close the server
break outer
case syscall.SIGINT:
globalLogger.Info().Msg("Received SIGINT, shutting down...")
break outer
case syscall.SIGHUP:
globalLogger.Info().Msg("Received SIGHUP, reloading configuration...")
newCfg, err := cfg.reloadConfig(a)
if err != nil {
globalLogger.Error().Err(err).Msg("Failed to reload configuration, using old configuration")
continue
}
cfg = newCfg
}
}
if memProfile != "" {
f, err := os.Create(memProfile)
if err != nil {
globalLogger.Fatal().Err(err).Msg("Could not create memory profile")
}
defer f.Close()
runtime.GC()
if err := pprof.WriteHeapProfile(f); err != nil {
globalLogger.Fatal().Err(err).Msg("Could not write memory profile")
}
}
}
07070100000025000081A400000000000000000000000168C6803E000000FA000000000000000000000000000000000000002500000000coraza-spoa-0.4.0+git3/renovate.json{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>corazawaf/renovate-config"
],
"packageRules": [
{
"matchPackageNames": [
"istio.io/istio"
],
"enabled": false
}
]
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!225 blocks