Skip to content

Commit

Permalink
feat: add webhook helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Palesandro committed Nov 9, 2023
1 parent e41a28b commit ba64a78
Show file tree
Hide file tree
Showing 7 changed files with 456 additions and 0 deletions.
23 changes: 23 additions & 0 deletions pkg/webhook/config/claimstypes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package config

type ClaimsMutatingHook struct {
// Name is the name of the webhook
Name string `json:"name"`
Type HookType `json:"type"`
AcceptedClaims []string `json:"claims"`
Config *WebhookConfig
}

type ClaimsValidatingHook struct {
// Name is the name of the webhook
Name string `json:"name"`
// To be modified to enum?
Type HookType `json:"type"`
AcceptedClaims []string `json:"claims"`
Config *WebhookConfig `json:"config"`
}

type TokenClaimsHooks struct {
MutatingHooks []ClaimsMutatingHook `json:"mutatingHooks"`
ValidatingHooks []ClaimsValidatingHook `json:"validatingHooks"`
}
14 changes: 14 additions & 0 deletions pkg/webhook/config/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package config

type WebhookConfig struct {
URL string `json:"url"`
InsecureSkipVerify bool `json:"insecureSkipVerify"`
TLSRootCAFile string `json:"tlsRootCAFile"`
ClientAuthentication *ClientAuthentication `json:"clientAuthentication"`
}

type ClientAuthentication struct {
ClientCertificateFile string `json:"clientCertificateFile"`
ClientKeyFile string `json:"clientKeyFile"`
ClientCAFile string `json:"clientCAFile"`
}
24 changes: 24 additions & 0 deletions pkg/webhook/config/connectortypes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package config

// HookRequestScope is the context of the request
type HookRequestScope struct {
// Headers is the headers of the request
Headers []string `json:"headers"`
// Params is the params of the request
Params []string `json:"params"`
}

type ConnectorFilterHook struct {
// Name is the name of the webhook
Name string `json:"name"`
// To be modified to enum?
Type HookType `json:"type"`
// RequestScope is the context of the request
RequestScope *HookRequestScope `json:"requestContext"`
// Config is the configuration of the webhook
Config *WebhookConfig `json:"config"`
}

type ConnectorFilterHooks struct {
FilterHooks []*ConnectorFilterHook `json:"filterHooks"`
}
7 changes: 7 additions & 0 deletions pkg/webhook/config/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package config

type HookType string

const (
External HookType = "external"
)
135 changes: 135 additions & 0 deletions pkg/webhook/helpers/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//go:generate go run -mod mod go.uber.org/mock/mockgen -destination=mock_helpers.go -package=helpers --source=helpers.go WebhookHTTPHelpers
package helpers

import (
"bytes"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"

"github.com/dexidp/dex/pkg/webhook/config"
)

type WebhookHTTPHelpers interface {
CallWebhook(jsonData []byte) ([]byte, error)
}

type webhookHTTPHelpersImpl struct {
transport *http.Transport
url string
}

var _ WebhookHTTPHelpers = &webhookHTTPHelpersImpl{}

func NewWebhookHTTPHelpers(cfg *config.WebhookConfig) (WebhookHTTPHelpers, error) {
if cfg == nil {
return nil, errors.New("webhook config is nil")
}
if cfg.URL == "" {
return nil, errors.New("webhook url is empty")
}
transport, err := createTransport(cfg)
if err != nil {
return nil, err
}
return &webhookHTTPHelpersImpl{
transport: transport,
url: cfg.URL,
}, nil
}

func (h *webhookHTTPHelpersImpl) CallWebhook(jsonData []byte) ([]byte, error) {
req, err := http.NewRequest("POST", h.url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}

req.Header.Set("Content-Type", "application/json")

client := &http.Client{Transport: h.transport}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response body: %v", err)
}

return body, nil
}

func createTransport(cfg *config.WebhookConfig) (*http.Transport, error) {
p, err := url.Parse(cfg.URL)
if err != nil {
return nil, fmt.Errorf("could not parse url: %v", err)
}
switch p.Scheme {
case "http":
return &http.Transport{}, nil
case "https":
return createHTTPSTransport(cfg)
default:
return nil, fmt.Errorf("unsupported scheme: %s", p.Scheme)
}
}

func createHTTPSTransport(cfg *config.WebhookConfig) (*http.Transport, error) {
var err error
rootCertPool := x509.NewCertPool()
if cfg.TLSRootCAFile != "" {
rootCertPool, err = readCACert(cfg.TLSRootCAFile)
if err != nil {
return nil, fmt.Errorf("failed to read file %q: %w", cfg.TLSRootCAFile, err)
}
}

tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: rootCertPool,
InsecureSkipVerify: cfg.InsecureSkipVerify,
MinVersion: tls.VersionTLS13,
},
}

if cfg.ClientAuthentication != nil {
clientCert, err := ReadCertificate(cfg.ClientAuthentication.ClientCertificateFile,
cfg.ClientAuthentication.ClientKeyFile)
if err != nil {
return nil, fmt.Errorf("failed to read certificate: %w", err)
}
tr.TLSClientConfig.Certificates = []tls.Certificate{*clientCert}
}

return tr, nil
}

func readCACert(caPath string) (*x509.CertPool, error) {
caCertPool := x509.NewCertPool()
// Load CA cert
caCert, err := os.ReadFile(caPath)
if err != nil {
return nil, err
}
caCertPool.AppendCertsFromPEM(caCert)
return caCertPool, nil
}

func ReadCertificate(certPath, keyPath string) (*tls.Certificate, error) {
cer, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, err
}
return &cer, nil
}
Loading

0 comments on commit ba64a78

Please sign in to comment.