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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
24 changes: 13 additions & 11 deletions api/meta/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import (
)

type GatewayCtrlConfig struct {
Namespace string `json:"namespace,omitempty"`
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
Agentless bool `json:"agentless,omitempty"` // Deprecated, has no effect
AgentRef string `json:"agentRef,omitempty"` // Deprecated, has no effect
DataplaneRef string `json:"dataplaneRef,omitempty"`
FRRRef string `json:"frrRef,omitempty"`
ToolboxRef string `json:"toolboxRef,omitempty"`
DataplaneMetricsPort uint16 `json:"dataplaneMetricsPort,omitempty"`
FRRMetricsPort uint16 `json:"frrMetricsPort,omitempty"`
Communities map[uint32]string `json:"communities,omitempty"`
FabricBFD bool `json:"fabricBFD,omitempty"`
Namespace string `json:"namespace,omitempty"`
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
Agentless bool `json:"agentless,omitempty"` // Deprecated, has no effect
AgentRef string `json:"agentRef,omitempty"` // Deprecated, has no effect
DataplaneRef string `json:"dataplaneRef,omitempty"`
DataplaneValidatorRef string `json:"dataplaneValidatorRef,omitempty"`
DataplaneValidatorTag string `json:"dataplaneValidatorTag,omitempty"`
FRRRef string `json:"frrRef,omitempty"`
ToolboxRef string `json:"toolboxRef,omitempty"`
DataplaneMetricsPort uint16 `json:"dataplaneMetricsPort,omitempty"`
FRRMetricsPort uint16 `json:"frrMetricsPort,omitempty"`
Communities map[uint32]string `json:"communities,omitempty"`
FabricBFD bool `json:"fabricBFD,omitempty"`
}

type AgentConfig struct {
Expand Down
24 changes: 19 additions & 5 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package main

import (
"context"
"crypto/tls"
"fmt"
"log/slog"
Expand All @@ -17,6 +18,10 @@
"github.com/go-logr/logr"
"github.com/lmittmann/tint"
"github.com/mattn/go-isatty"
"go.githedgehog.com/gateway/api/meta"
"go.githedgehog.com/gateway/pkg/ctrl"
"go.githedgehog.com/gateway/pkg/version"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
Expand All @@ -27,15 +32,17 @@
"sigs.k8s.io/controller-runtime/pkg/webhook"
kyaml "sigs.k8s.io/yaml"

"go.githedgehog.com/gateway/pkg/ctrl"
"go.githedgehog.com/gateway/pkg/version"

gatewayv1alpha1 "go.githedgehog.com/gateway/api/gateway/v1alpha1"
gwintv1alpha1 "go.githedgehog.com/gateway/api/gwint/v1alpha1"
"go.githedgehog.com/gateway/api/meta"
// +kubebuilder:scaffold:imports
)

const (
CAPath = "/ca/ca.crt"
CredsPath = "/creds/" + corev1.DockerConfigJsonKey
CacheDir = "/cache/v1"
)

var scheme = runtime.NewScheme()

func init() {
Expand Down Expand Up @@ -84,6 +91,13 @@
return fmt.Errorf("unmarshalling config file: %w", err)
}

ctx, close := context.WithTimeout(context.Background(), 5*time.Minute)

Check failure on line 94 in cmd/main.go

View workflow job for this annotation

GitHub Actions / test-build

redefines-builtin-id: redefinition of the built-in function close (revive)

Check failure on line 94 in cmd/main.go

View workflow job for this annotation

GitHub Actions / test-build

redefines-builtin-id: redefinition of the built-in function close (revive)
defer close()
Comment on lines +94 to +95
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The variable name close shadows the built-in close function. Consider renaming it to cancel which is the conventional name for context cancellation functions.

Suggested change
ctx, close := context.WithTimeout(context.Background(), 5*time.Minute)
defer close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

Copilot uses AI. Check for mistakes.
validator, err := ctrl.NewValidator(ctx, CredsPath, CAPath, cfg.DataplaneValidatorRef, cfg.DataplaneValidatorTag)
if err != nil {
return fmt.Errorf("creating validator: %w", err)
}

// Disabling http/2 will prevent from being vulnerable to the HTTP/2 Stream Cancellation and Rapid Reset CVEs.
// For more information see:
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
Expand Down Expand Up @@ -143,7 +157,7 @@
}

// Webhooks
if err := ctrl.SetupGatewayWebhookWith(mgr); err != nil {
if err := ctrl.SetupGatewayWebhookWith(mgr, validator); err != nil {
return fmt.Errorf("setting up gateway webhook: %w", err)
}
if err := ctrl.SetupGatewayGroupWebhookWith(mgr); err != nil {
Expand Down
12 changes: 12 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,22 @@ spec:
- name: config
mountPath: /etc/hedgehog/gateway-ctrl
readOnly: true
- name: fab-ca
mountPath: /ca
readOnly: true
- name: creds
mountPath: /creds
readOnly: true
volumes:
- name: config
configMap:
# TODO it shouldn't be static, prefixName should be used?
name: gateway-ctrl-config
- name: fab-ca
configMap:
name: fab-ca # TODO pass using values
- name: creds
secret:
secretName: registry-user-reader-docker # TODO pass using values
serviceAccountName: ctrl
terminationGracePeriodSeconds: 10
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ require (
github.com/onsi/ginkgo/v2 v2.27.5
github.com/onsi/gomega v1.39.0
github.com/stretchr/testify v1.11.1
github.com/tetratelabs/wazero v1.11.0
k8s.io/api v0.35.0
k8s.io/apimachinery v0.35.0
k8s.io/client-go v0.35.0
k8s.io/klog/v2 v2.130.1
k8s.io/utils v0.0.0-20260108192941-914a6e750570
oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/controller-runtime v0.23.1
sigs.k8s.io/yaml v1.6.0
)
Expand Down Expand Up @@ -50,6 +52,8 @@ require (
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nxadm/tail v1.4.11 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE
github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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=
Expand Down Expand Up @@ -150,6 +154,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
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=
Expand Down Expand Up @@ -261,6 +267,8 @@ k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZ
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE=
Expand Down
4 changes: 3 additions & 1 deletion pkg/ctrl/gateway_wh.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import (

type GatewayWebhook struct {
kclient.Reader
val *Validator
}

func SetupGatewayWebhookWith(mgr kctrl.Manager) error {
func SetupGatewayWebhookWith(mgr kctrl.Manager, val *Validator) error {
w := &GatewayWebhook{
Reader: mgr.GetClient(),
val: val,
}

if err := kctrl.NewWebhookManagedBy(mgr, &gwapi.Gateway{}).
Expand Down
128 changes: 128 additions & 0 deletions pkg/ctrl/validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2025 Hedgehog
// SPDX-License-Identifier: Apache-2.0

package ctrl

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"log/slog"
"net/http"
"os"
"path/filepath"

"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content/file"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/credentials"
"oras.land/oras-go/v2/registry/remote/retry"
)

type Validator struct {
runtime wazero.Runtime
compiled wazero.CompiledModule
}

func NewValidator(ctx context.Context, credsPath, caPath, ref, tag string) (*Validator, error) {
v := &Validator{}

if ref == "" && tag == "" {
slog.Info("Skipping Dataplane validator as it is not configured")

return nil, nil //nolint:nilnil
}

slog.Info("Loading dataplane validator", "version", tag)

slog.Debug("Downloading dataplane validator", "ref", ref)

storeOpts := credentials.StoreOptions{}
credStore, err := credentials.NewStore(credsPath, storeOpts)
Comment on lines +44 to +45
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The storeOpts variable is initialized with an empty struct and never modified. Consider using credentials.StoreOptions{} directly inline on line 45 to reduce unnecessary variable declaration.

Suggested change
storeOpts := credentials.StoreOptions{}
credStore, err := credentials.NewStore(credsPath, storeOpts)
credStore, err := credentials.NewStore(credsPath, credentials.StoreOptions{})

Copilot uses AI. Check for mistakes.
if err != nil {
return nil, fmt.Errorf("creating docker credential store for %s: %w", credsPath, err)
}

ca, err := os.ReadFile(caPath)
if err != nil {
return nil, fmt.Errorf("reading CA cert %s: %w", caPath, err)
}

rootCAs := x509.NewCertPool()
if !rootCAs.AppendCertsFromPEM(ca) {
return nil, fmt.Errorf("appending CA cert to rootCAs: %w", err)
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The error variable err is being wrapped here, but it will always be nil at this point since it was only set on line 51 (the ReadFile call). If AppendCertsFromPEM fails, you should return a new error without wrapping, such as fmt.Errorf("failed to append CA cert to rootCAs\").

Suggested change
return nil, fmt.Errorf("appending CA cert to rootCAs: %w", err)
return nil, fmt.Errorf("failed to append CA cert %s to rootCAs", caPath)

Copilot uses AI. Check for mistakes.
}

baseTransport := http.DefaultTransport.(*http.Transport).Clone()
baseTransport.TLSClientConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: false,
RootCAs: rootCAs,
}

repo, err := remote.NewRepository(ref)
if err != nil {
return nil, fmt.Errorf("creating oras remote repo %s: %w", ref, err)
}

repo.Client = &auth.Client{
Client: &http.Client{
Transport: retry.NewTransport(baseTransport),
},
Cache: auth.DefaultCache,
Credential: credentials.Credential(credStore),
}

tmp, err := os.MkdirTemp("", "download-*")
if err != nil {
return nil, fmt.Errorf("creating temp dir: %w", err)
}
defer os.RemoveAll(tmp)

fs, err := file.New(tmp)
if err != nil {
return nil, fmt.Errorf("creating oras file store %s: %w", tmp, err)
}
defer fs.Close()

_, err = oras.Copy(ctx, repo, tag, fs, "", oras.CopyOptions{
CopyGraphOptions: oras.CopyGraphOptions{
Concurrency: 1,
},
})
if err != nil {
return nil, fmt.Errorf("downloading files from %s:%s: %w", ref, tag, err)
}

wasmBytes, err := os.ReadFile(filepath.Join(tmp, "dataplane-validator"))
if err != nil {
return nil, fmt.Errorf("reading WASM file: %w", err)
}

slog.Debug("Setting up WASM runtime")

v.runtime = wazero.NewRuntime(ctx)
wasi_snapshot_preview1.MustInstantiate(ctx, v.runtime)

slog.Debug("Compiling dataplane validator")

v.compiled, err = v.runtime.CompileModule(ctx, wasmBytes)
if err != nil {
return nil, fmt.Errorf("compiling WASM module: %w", err)
}

return v, nil
}

func (v *Validator) Close(ctx context.Context) {
if err := v.compiled.Close(ctx); err != nil {
slog.Warn("Error closing compiled validator module", "err", err.Error())
}
if err := v.runtime.Close(ctx); err != nil {
slog.Warn("Error closing validator runtime", "err", err.Error())
}
Comment on lines +122 to +127
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The Close method doesn't check if v.compiled or v.runtime are nil before attempting to close them. If NewValidator returns early (lines 34-38), these fields will be nil and calling Close will panic. Add nil checks before calling Close on these fields.

Suggested change
if err := v.compiled.Close(ctx); err != nil {
slog.Warn("Error closing compiled validator module", "err", err.Error())
}
if err := v.runtime.Close(ctx); err != nil {
slog.Warn("Error closing validator runtime", "err", err.Error())
}
if v == nil {
return
}
if v.compiled != nil {
if err := v.compiled.Close(ctx); err != nil {
slog.Warn("Error closing compiled validator module", "err", err.Error())
}
}
if v.runtime != nil {
if err := v.runtime.Close(ctx); err != nil {
slog.Warn("Error closing validator runtime", "err", err.Error())
}
}

Copilot uses AI. Check for mistakes.
}
4 changes: 4 additions & 0 deletions vendor/github.com/opencontainers/go-digest/.mailmap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions vendor/github.com/opencontainers/go-digest/.pullapprove.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vendor/github.com/opencontainers/go-digest/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading