Skip to content
Open
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
2 changes: 2 additions & 0 deletions internal/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ func mapSimpleLinterRules(linterSettings *pkg.LintersSettings, configSettings *c

// Hooks rules
linterSettings.Hooks.Rules.HooksRule.SetLevel("", configSettings.Hooks.Impact)
linterSettings.Hooks.Rules.TLSCertificateRule.SetLevel("", configSettings.Hooks.Impact)
}

// mapExclusionRulesAndSettings maps exclusion rules and additional linter settings
Expand Down Expand Up @@ -469,6 +470,7 @@ func mapRBACExclusions(linterSettings *pkg.LintersSettings, configSettings *conf
// mapHooksSettings maps Hooks linter settings
func mapHooksSettings(linterSettings *pkg.LintersSettings, configSettings *config.LintersSettings) {
linterSettings.Hooks.IngressRuleSettings.Disable = configSettings.Hooks.Ingress.Disable
linterSettings.Hooks.TLSCertificateRuleSettings.Disable = configSettings.Hooks.TLSCertificate.Disable
}

// mapModuleExclusionsAndSettings maps Module linter exclusions and settings
Expand Down
11 changes: 8 additions & 3 deletions pkg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,20 @@ type RBACExcludeRules struct {
}
type HooksLinterConfig struct {
LinterConfig
Rules HooksLinterRules
IngressRuleSettings IngressRuleSettings
Rules HooksLinterRules
IngressRuleSettings IngressRuleSettings
TLSCertificateRuleSettings TLSCertificateRuleSettings
}
type HooksLinterRules struct {
HooksRule RuleConfig
HooksRule RuleConfig
TLSCertificateRule RuleConfig
}
type IngressRuleSettings struct {
Disable bool
}
type TLSCertificateRuleSettings struct {
Disable bool
}

type ModuleLinterConfig struct {
LinterConfig
Expand Down
8 changes: 7 additions & 1 deletion pkg/config/linters_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ type ContainerExcludeRules struct {
}

type HooksSettings struct {
Ingress HooksIngressRuleSetting `mapstructure:"ingress"`
Ingress HooksIngressRuleSetting `mapstructure:"ingress"`
TLSCertificate HooksTLSCertificateRuleSetting `mapstructure:"tls-certificate"`

Impact string `mapstructure:"impact"`
}
Expand All @@ -84,6 +85,11 @@ type HooksIngressRuleSetting struct {
Disable bool `mapstructure:"disable"`
}

type HooksTLSCertificateRuleSetting struct {
// disable tls-certificate rule completely
Disable bool `mapstructure:"disable"`
}

type ImageSettings struct {
ExcludeRules ImageExcludeRules `mapstructure:"exclude-rules"`

Expand Down
82 changes: 82 additions & 0 deletions pkg/linters/hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Hooks are Go or Python scripts that react to Kubernetes resource changes and imp
| Rule | Description | Configurable | Default |
|------|-------------|--------------|---------|
| [ingress](#ingress) | Validates copy_custom_certificate hook presence for Ingress resources | ✅ | enabled |
| [tls-certificate](#tls-certificate) | Detects invalid self-signed certificate generation in Go hooks | ✅ | enabled |

## Rule Details

Expand Down Expand Up @@ -104,6 +105,85 @@ linters-settings:
disable: true
```

### tls-certificate

**Purpose:** Detects places in Go hooks that generate invalid self-signed TLS certificates using the `go_lib/hooks/tls_certificate` helpers (`RegisterInternalTLSHook` / `GenerateSelfSignedCert`).

**Description:**

Self-signed certificates produced by these helpers had defects that caused validation failures outside of Go/kube-apiserver (`openssl verify`, Trivy, Java keystores, MaxPatrol). This rule statically scans Go hook source for the known causes, based on [deckhouse/deckhouse#20138](https://github.com/deckhouse/deckhouse/pull/20138).

**What it checks:**

The rule only inspects `.go` files in the module's `hooks/` directory that import:

```go
"github.com/deckhouse/deckhouse/go_lib/hooks/tls_certificate"
```

For those files it reports:

1. **Bogus `"requestheader-client"` usage.** This string does not exist in cfssl's KeyUsage/ExtKeyUsage maps, so cfssl silently discards it and emits a certificate with an empty `ExtendedKeyUsage` extension. Strict validators require at least `serverAuth`. Use `"server auth"` instead.
2. **`WithGroups` on a leaf certificate.** Passing `WithGroups(...)` into a `GenerateSelfSignedCert(...)` call copies the CA's `Organization` onto the leaf, recreating the `Subject == Issuer` (depth-0 self-signed) collision that OpenSSL rejects with `X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT`.

**Why it matters:**

Certificates that pass Go's lenient verification still fail in OpenSSL, Trivy, and other strict validators, breaking webhooks and security scans in environments outside of kube-apiserver.

**Examples:**

❌ **Incorrect** - bogus usage that drops the EKU extension:

```go
import "github.com/deckhouse/deckhouse/go_lib/hooks/tls_certificate"

var _ = tls_certificate.RegisterInternalTLSHook(tls_certificate.GenSelfSignedTLSHookConf{
Usages: []string{"requestheader-client"}, // no EKU is emitted
// ...
})
```

❌ **Incorrect** - `WithGroups` on the leaf recreates Subject == Issuer:

```go
cert, _ := tls_certificate.GenerateSelfSignedCert(
"leaf",
ca,
tls_certificate.WithGroups("Deckhouse"), // copies CA Organization onto the leaf
)
```

✅ **Correct**:

```go
var _ = tls_certificate.RegisterInternalTLSHook(tls_certificate.GenSelfSignedTLSHookConf{
Usages: []string{"server auth"},
// ...
})

cert, _ := tls_certificate.GenerateSelfSignedCert("leaf", ca)
```

**Error:**

```
Invalid certificate usage "requestheader-client" produces a certificate with an empty ExtendedKeyUsage extension and is rejected by strict validators. Use "server auth" instead.
```

```
WithGroups applied to a leaf certificate via GenerateSelfSignedCert copies the CA Organization onto the leaf, recreating the Subject == Issuer (depth-0 self-signed) collision rejected by OpenSSL. Remove WithGroups from the leaf certificate.
```

**Configuration:**

```yaml
# .dmt.yaml
linters-settings:
hooks:
tls-certificate:
disable: false # Enable/disable the tls-certificate rule
```

## Configuration

The Hooks linter can be configured at both the module level and for individual rules.
Expand Down Expand Up @@ -150,6 +230,8 @@ linters-settings:
# Rule-specific settings
ingress:
disable: false
tls-certificate:
disable: false
```

### Configuration in Module Directory
Expand Down
3 changes: 3 additions & 0 deletions pkg/linters/hooks/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ func (h *Hooks) Run(m *module.Module) {
for _, object := range m.GetStorage() {
r.CheckIngressCopyCustomCertificateRule(m, object, errorList)
}

rules.NewTLSCertificateRule(h.cfg.TLSCertificateRuleSettings.Disable).
CheckTLSCertificateHooks(m, errorList.WithMaxLevel(h.cfg.Rules.TLSCertificateRule.GetLevel()))
}

func (h *Hooks) Name() string {
Expand Down
9 changes: 9 additions & 0 deletions pkg/linters/hooks/rules/testdata/tls/invalid_usage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package tls

import (
"github.com/deckhouse/deckhouse/go_lib/hooks/tls_certificate"
)

var _ = tls_certificate.RegisterInternalTLSHook(tls_certificate.GenSelfSignedTLSHookConf{
Usages: []string{"requestheader-client"},
})
12 changes: 12 additions & 0 deletions pkg/linters/hooks/rules/testdata/tls/no_import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package tls

// This file uses the same bogus strings/options but does NOT import the
// tls_certificate library, so the rule must skip it entirely.

func WithGroups(string) string { return "" }

func GenerateSelfSignedCert(args ...any) string { return "" }

var usages = []string{"requestheader-client"}

var skipped = GenerateSelfSignedCert("leaf", WithGroups("Deckhouse"))
11 changes: 11 additions & 0 deletions pkg/linters/hooks/rules/testdata/tls/valid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package tls

import (
"github.com/deckhouse/deckhouse/go_lib/hooks/tls_certificate"
)

var _ = tls_certificate.RegisterInternalTLSHook(tls_certificate.GenSelfSignedTLSHookConf{
Usages: []string{"server auth"},
})

var validLeaf, _ = tls_certificate.GenerateSelfSignedCert("leaf", nil)
11 changes: 11 additions & 0 deletions pkg/linters/hooks/rules/testdata/tls/with_groups.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package tls

import (
"github.com/deckhouse/deckhouse/go_lib/hooks/tls_certificate"
)

var leaf, _ = tls_certificate.GenerateSelfSignedCert(
"leaf",
nil,
tls_certificate.WithGroups("Deckhouse"),
)
Loading
Loading