Skip to content

Conversation

@LaurenceJJones
Copy link
Contributor

Opening a PR for thoughts if this should like in this package?

@LaurenceJJones
Copy link
Contributor Author

LaurenceJJones commented Apr 15, 2024

Adding some context around this PR, initially when we created the AppSec component within CrowdSec we provided a base idea for what the creators of the integration should aim for. However, since we find issues or things we need to change such as the user-agent header we would have to go back and chase the integrations to update their code. (which isn't hard see traefik PR maxlerebourg/crowdsec-bouncer-traefik-plugin#149 pretty much issued a fix within hours of opening it, but if we forgot to inform them then its bad on our part, as humans we tend to forget)

The aim of this PR is to take the responsibility of parsing the incoming client request to an appropriate request for AppSec component away from the integrator and we take some responsibility for setting the correct headers etc etc.

The way to use this is create an csbouncer.AppSec{} struct this struct exposes a config reader the same as the current csbouncer.StreamBouncer{} does and will attempt to inherit the api_key set on the base of the bouncer configuration. To configure the AppSec the yaml should look something like:

api_key: {{ API_KEY }}
appsec_config:
  url: http://localhost:7422/

As you can see the appsec configuration is stored within its own directive and is an extension of the bouncer configuration so any integrator can do this

var err error
configExpanded := csstring.StrictExpand(string(configMerged), os.LookupEnv)
reader := strings.NewReader(configExpanded)
bouncer := &csbouncer.StreamBouncer{}
err = bouncer.ConfigReader(reader)
if err != nil {
  return err
}
appsec := &csbouncer.AppSec{}
err = appsec.ConfigReader(reader)
if err != nil {
  return err
}

Then simply the integrator can use the Forward function to pass in the incoming client request which internally is parsed to a appsec compatible request and returns the response from the appsec

You can see examples shown within the example folder which is a simple http server which creates a middleware function

@LaurenceJJones LaurenceJJones marked this pull request as ready for review April 15, 2024 12:22
@LaurenceJJones LaurenceJJones changed the title wip: appsec lib feat: appsec lib Apr 15, 2024
crowdsecAppsecUserAgent = "X-Crowdsec-Appsec-User-Agent"
)

type Timeout struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to AppsecTimeout as Timeout might be misunderstood as also applying to the connection to LAPI itself ?

)

type Timeout struct {
ConnectTimeout *int `yaml:"connect_timeout"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably transform those to time.Duration (and let the user of the library handle the parsing/validation).
With the way things are currently, it's not possible to have a timeout lower than 1s, which is likely way too long for the majority of users.

InsecureSkipVerify: *w.AppSecConfig.InsecureSkipVerify,
RootCAs: caCertPool,
},
TLSHandshakeTimeout: time.Duration(*w.AppSecConfig.Timeout.TLSHandshakeTimeout) * time.Second,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded second is way too long (see my comment above in the Timeout struct)

func (w *AppSec) ParseClientReq(ctx context.Context, clientReq *http.Request, ipOverride string) (*http.Request, error) {
var req *http.Request

if clientReq.Body != nil && clientReq.ContentLength > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need some kind of override for the body: in some situations, we do not want to read the entire body in memory (for example, a file upload of a few GB).
To keep things simple in the library, it could just be a true/false flag, and we let the user set it (main drawback being this means different bouncers are very likely to have different configuration/support).

Cleaner solution would be to provide additional configuration with for example body size, URL prefix, source IP and so on

(this is an issue we already have in the nginx appsec integration)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants