goharbor/harbor · warning · lib/errors.Error

BAD_REQUEST

BAD_REQUEST

Error message

audit log forward endpoint should be configured before enable skip audit log in database

What it means

When updating Harbor configurations, enabling skip_audit_log_database=true is only legal if an audit_log_forward_endpoint is also set (in the same payload or already persisted). The config controller verifies this pair and returns BAD_REQUEST, because skipping the DB audit sink without a forward endpoint would silently drop audit logs.

Source

Thrown at src/controller/config/controller.go:165

}

func verifySkipAuditLogCfg(ctx context.Context, cfgs map[string]any, mgr config.Manager) error {
	updated := false
	endPoint := mgr.Get(ctx, common.AuditLogForwardEndpoint).GetString()
	skipAuditDB := mgr.Get(ctx, common.SkipAuditLogDatabase).GetBool()

	if skip, exist := cfgs[common.SkipAuditLogDatabase]; exist {
		skipAuditDB = skip.(bool)
		updated = true
	}
	if endpoint, exist := cfgs[common.AuditLogForwardEndpoint]; exist {
		endPoint = endpoint.(string)
		updated = true
	}

	if updated {
		if skipAuditDB && len(endPoint) == 0 {
			return errors.BadRequestError(errors.New("audit log forward endpoint should be configured before enable skip audit log in database"))
		}
	}
	return nil
}

// verifyValueLengthCfg verifies the cfgs which need to check the value max length to align with frontend.
func verifyValueLengthCfg(_ context.Context, cfgs map[string]any) error {
	maxValue := maxValueLimitedByLength(common.UIMaxLengthLimitedOfNumber)
	validateCfgs := []string{
		common.TokenExpiration,
		common.RobotTokenDuration,
		common.SessionTimeout,
	}

	for _, c := range validateCfgs {
		if v, exist := cfgs[c]; exist {
			// the cfgs is unmarshal from json string, the number type will be float64
			if vf, ok := v.(float64); ok {

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. Set audit_log_forward_endpoint to a reachable listener address in the same or an earlier update, then set skip_audit_log_database=true.
  2. Verify current values with GET /api/v2/configs and compare with your PUT payload to ensure the endpoint is not being overwritten to empty.
  3. Confirm the forward endpoint actually receives logs after the change, so audits are not lost.

Example fix

# before
PUT /api/v2/configs
{"skip_audit_log_database": true}

# after
{"audit_log_forward_endpoint": "127.0.0.1:8514", "skip_audit_log_database": true}
Defensive patterns

Strategy: validation

Validate before calling

// client-side invariant before PUT /api/v2/configs:
func validateAuditCfg(current, update map[string]any) error {
    skip := update["skip_audit_log_database"]
    if skip == nil { skip = current["skip_audit_log_database"] }
    ep := update["audit_log_forward_endpoint"]
    if ep == nil { ep = current["audit_log_forward_endpoint"] }
    if skip == true && (ep == nil || ep == "") {
        return fmt.Errorf("set audit_log_forward_endpoint before enabling skip_audit_log_database")
    }
    return nil
}

Try / catch

if err := configCtl.Update(ctx, cfgs, false); err != nil {
    if liberrors.IsErr(err, liberrors.BadRequestCode) &&
        strings.Contains(err.Error(), "audit log forward endpoint") {
        // fix payload: add endpoint, resend
    }
    return err
}

Prevention

When it happens

Trigger: PUT /api/v2/configs with {"skip_audit_log_database": true} while audit_log_forward_endpoint is absent or sent as an empty string in the same request.

Common situations: Migrating audit output to a log forwarder (e.g. syslog endpoint) but enabling the skip flag first; a config-management tool (Terraform/Ansible) sends only the changed key; upgrading to a Harbor version that introduced the flag.

Related errors


AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16). Data as JSON: /api/errors/7fd52a66332bcb1e. Report an issue: GitHub.