Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,23 @@ cscli: ## Build cscli
crowdsec: ## Build crowdsec
@$(MAKE) -C $(CROWDSEC_FOLDER) build $(MAKE_FLAGS)

# Regenerate the WAF challenge JS artifacts: the bundled fpscanner source
# (fpscanner/bundle.js), the obfuscator runtime (obfuscate/index.wasm.gz),
# and the build-time pre-obfuscated initial bundle (initial_bundle.js.gz).
# These are committed to the tree so a normal `make build` does NOT rebuild
# them — only run this target after intentionally changing challenge.js,
# fpscanner sources, the obfuscator wrapper, or the dynamic-module template.
#
# Requires `javy` (https://github.com/bytecodealliance/javy/releases) on
# PATH. The full obfuscation pass is slow (~1 minute) because it runs
# javascript-obfuscator's "high-obfuscation" preset over the full bundle;
# this is the cost we are paying once at generate time so the runtime
# doesn't pay it on every startup.
.PHONY: generate-challenge-js
generate-challenge-js: ## Regenerate WAF challenge JS bundles (requires javy on PATH)
@command -v javy >/dev/null 2>&1 || (echo "Error: javy is not installed. Download it from https://github.com/bytecodealliance/javy/releases and put it on PATH." && exit 1)
$(GO) generate ./pkg/appsec/challenge/js/...

testenv:
ifeq ($(TEST_LOCAL_ONLY),)
@echo 'NOTE: You need to run "make localstack" in a separate shell, "make localstack-stop" to terminate it; or define the envvar TEST_LOCAL_ONLY to some value.'
Expand Down
46 changes: 45 additions & 1 deletion pkg/acquisition/modules/appsec/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,31 @@ type Configuration struct {
AppsecConfigs []string `yaml:"appsec_configs"`
AppsecConfigPath string `yaml:"appsec_config_path"`
AuthCacheDuration *time.Duration `yaml:"auth_cache_duration"`
// ChallengeMasterSecret is the long-lived secret used by the WAF
// challenge runtime to sign tickets / PoW MACs and seal challenge
// cookies. In a distributed (multi-WAF) deployment, all instances MUST
// share the same value so that a challenge issued by one instance
// validates against another. May be a hex-encoded byte string or a raw
// passphrase; minimum 32 bytes / characters. If unset, an ephemeral
// random secret is generated at startup (suitable for single-instance
// deployments only — restarts invalidate outstanding challenge cookies).
ChallengeMasterSecret string `yaml:"challenge_master_secret"`

// ChallengeKeyRotationInterval controls how often the per-epoch
// challenge key advances. All instances in a distributed setup MUST
// agree on this value to derive identical per-epoch keys.
ChallengeKeyRotationInterval *time.Duration `yaml:"challenge_key_rotation_interval"`

// ChallengeMaxLiveEpochs is how many past epochs (in addition to the
// current one) the keyring continues to accept. Sized so any submission
// within the freshness window has a non-evicted epoch.
ChallengeMaxLiveEpochs int `yaml:"challenge_max_live_epochs"`

// ChallengeCookieTTL controls how long a successful-challenge cookie
// stays valid. Decoupled from the keyring rotation window so cookies
// can be long-lived (e.g. 24h) without widening the per-epoch ticket-
// forgery exposure. Defaults to 12h when unset.
ChallengeCookieTTL *time.Duration `yaml:"challenge_cookie_ttl"`
configuration.DataSourceCommonCfg `yaml:",inline"`
}

Expand Down Expand Up @@ -191,7 +216,26 @@ func (w *Source) Configure(ctx context.Context, yamlConfig []byte, logger *log.E

if appsecRuntime.NeedWASMVM {
logger.Info("Initializing WASM runtime for challenge obfuscation")
challengeRuntime, err := challenge.NewChallengeRuntime(ctx)

var challengeOpts []challenge.Option
if w.config.ChallengeMasterSecret != "" {
secret, err := challenge.ParseConfiguredSecret(w.config.ChallengeMasterSecret)
if err != nil {
return fmt.Errorf("invalid challenge_master_secret: %w", err)
}
challengeOpts = append(challengeOpts, challenge.WithMasterSecret(secret))
}
if w.config.ChallengeKeyRotationInterval != nil {
challengeOpts = append(challengeOpts, challenge.WithRotationInterval(*w.config.ChallengeKeyRotationInterval))
}
if w.config.ChallengeMaxLiveEpochs > 0 {
challengeOpts = append(challengeOpts, challenge.WithMaxLiveEpochs(w.config.ChallengeMaxLiveEpochs))
}
if w.config.ChallengeCookieTTL != nil {
challengeOpts = append(challengeOpts, challenge.WithCookieTTL(*w.config.ChallengeCookieTTL))
}

challengeRuntime, err := challenge.NewChallengeRuntime(ctx, challengeOpts...)
if err != nil {
return fmt.Errorf("unable to create challenge runtime: %w", err)
}
Expand Down
Loading
Loading