Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
Signed-off-by: Frank Jogeleit <[email protected]>
  • Loading branch information
Frank Jogeleit committed Dec 26, 2023
1 parent 5774ce3 commit 05cc9e7
Show file tree
Hide file tree
Showing 47 changed files with 655 additions and 280 deletions.
76 changes: 45 additions & 31 deletions backend/pkg/config/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import (
k8s "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"

"github.com/kyverno/policy-reporter-ui/pkg/kubernetes/namespaces"
"github.com/kyverno/policy-reporter-ui/pkg/core/client"
"github.com/kyverno/policy-reporter-ui/pkg/core/proxy"
"github.com/kyverno/policy-reporter-ui/pkg/kubernetes/secrets"
"github.com/kyverno/policy-reporter-ui/pkg/logging"
"github.com/kyverno/policy-reporter-ui/pkg/proxy"
"github.com/kyverno/policy-reporter-ui/pkg/server"
"github.com/kyverno/policy-reporter-ui/pkg/server/api"
"github.com/kyverno/policy-reporter-ui/pkg/utils"
Expand All @@ -32,23 +32,51 @@ type Resolver struct {
kubeConfig string
devMode bool

secrets secrets.Client
namespaces namespaces.Client
secrets secrets.Client

k8sConfig *rest.Config
clientset *k8s.Clientset
}

func (r *Resolver) ExternalProxies(ctx context.Context, cluster Cluster) (map[string]*httputil.ReverseProxy, error) {
func (r *Resolver) CoreClient(cluster Cluster) (*client.Client, error) {
options := []client.ClientOption{
client.WithBaseURL(cluster.Host),
}

if cluster.Certificate != "" {
options = append(options, client.WithCertificate(cluster.Certificate))
} else if cluster.SkipTLS {
options = append(options, client.WithSkipTLS())
}

if cluster.BasicAuth.Username != "" {
options = append(options, client.WithBaseAuth(client.BasicAuth{
Username: cluster.BasicAuth.Username,
Password: cluster.BasicAuth.Password,
}))
}

if r.config.Logging.Enabled {
options = append(options, client.WithLogging())
}

return client.New(options)
}

func (r *Resolver) LoadClusterSecret(ctx context.Context, cluster Cluster) (Cluster, error) {
if cluster.SecretRef != "" {
values, err := r.LoadSecret(ctx, cluster.SecretRef)
if err != nil {
return nil, err
return cluster, err
}

cluster = cluster.FromValues(values)
}

return cluster, nil
}

func (r *Resolver) ExternalProxies(cluster Cluster) (map[string]*httputil.ReverseProxy, error) {
if cluster.Host == "" {
return nil, ErrMissingAPI
}
Expand Down Expand Up @@ -172,21 +200,6 @@ func (r *Resolver) Clientset() (*k8s.Clientset, error) {
return r.clientset, nil
}

func (r *Resolver) NamespaceClient() (namespaces.Client, error) {
if r.namespaces != nil {
return r.namespaces, nil
}

clientset, err := r.Clientset()
if err != nil {
return nil, err
}

r.namespaces = namespaces.NewClient(clientset.CoreV1().Namespaces())

return r.namespaces, nil
}

func (r *Resolver) InitSecretClient() error {
clientset, err := r.Clientset()
if err != nil {
Expand All @@ -210,13 +223,21 @@ func (r *Resolver) Server(ctx context.Context) *server.Server {
serv := server.NewServer(engine, r.config.Server.Port)

for _, cluster := range r.config.Clusters {
proxies, err := r.ExternalProxies(ctx, cluster)
cluster, err := r.LoadClusterSecret(ctx, cluster)
if err != nil {
zap.L().Error("failed to load cluster secret", zap.Error(err), zap.String("cluser", cluster.Name))
continue
}

proxies, err := r.ExternalProxies(cluster)
if err != nil {
zap.L().Error("failed to resolve proxies", zap.Error(err), zap.String("cluser", cluster.Name))
continue
}

serv.RegisterCluster(cluster.Name, proxies)
client, err := r.CoreClient(cluster)

serv.RegisterCluster(cluster.Name, client, proxies)
}

if !r.config.UI.Disabled {
Expand All @@ -227,13 +248,6 @@ func (r *Resolver) Server(ctx context.Context) *server.Server {
serv.RegisterAPI(MapConfig(r.config))

if len(r.config.CustomBoards) > 0 {
client, err := r.NamespaceClient()
if err != nil {
zap.L().Error("failed to create namespace client", zap.Error(err))

return serv
}

configs := make(map[string]api.CustomBoard, len(r.config.CustomBoards))
for _, c := range r.config.CustomBoards {
id := slug.Make(c.Name)
Expand All @@ -253,7 +267,7 @@ func (r *Resolver) Server(ctx context.Context) *server.Server {
}}
}

serv.RegisterCustomBoards(client, configs)
serv.RegisterCustomBoards(configs)
}

return serv
Expand Down
94 changes: 94 additions & 0 deletions backend/pkg/core/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package client

import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"io"
"net"
"net/http"
"time"
)

type BasicAuth struct {
Username string
Password string
}

type Client struct {
baseURL string
http *http.Client
auth *BasicAuth
}

func (c *Client) ResolveNamespaceSelector(ctx context.Context, selector map[string]string) ([]string, error) {
resp, err := c.post(ctx, "/v2/namespaces/resolve-selector", selector)
if err != nil {
return nil, err
}
defer resp.Body.Close()

return decodeList[string](resp.Body)
}

// CreateJSONRequest for the given configuration
func (c *Client) post(ctx context.Context, path string, payload interface{}) (*http.Response, error) {
body := new(bytes.Buffer)

if err := json.NewEncoder(body).Encode(payload); err != nil {
return nil, err
}

req, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+path, body)
if err != nil {
return nil, err
}

if c.auth != nil {
req.SetBasicAuth(c.auth.Username, c.auth.Password)
}

req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("User-Agent", "Policy Reporter UI")

return c.http.Do(req)
}

func decodeList[T any](r io.Reader) ([]T, error) {
list := make([]T, 0)
err := json.NewDecoder(r).Decode(&list)

return list, err
}

func New(options []ClientOption) (*Client, error) {
client := &Client{
http: newHTTPClient(),
}

for _, o := range options {
if err := o(client); err != nil {
return nil, err
}
}

return client, nil
}

func newHTTPClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{},
},
Timeout: 10 * time.Second,
}
}
38 changes: 38 additions & 0 deletions backend/pkg/core/client/logroundtripper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package client

import (
"fmt"
"net/http"
"net/http/httputil"

"go.uber.org/zap"
)

func newLoggingRoundTripper(roundTripper http.RoundTripper) http.RoundTripper {
return &logRoundTripper{roundTripper: roundTripper}
}

type logRoundTripper struct {
roundTripper http.RoundTripper
}

func (rt *logRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
logger := zap.L()
if logger.Core().Enabled(zap.DebugLevel) {
if info, err := httputil.DumpRequest(req, true); err == nil {
logger.Debug(fmt.Sprintf("Sending request: %s", string(info)))
if err != nil {
return nil, err
}
}
}
resp, err := rt.roundTripper.RoundTrip(req)
if resp != nil {
if logger.Core().Enabled(zap.DebugLevel) {
if info, err := httputil.DumpResponse(resp, true); err == nil {
logger.Debug(fmt.Sprintf("Received response: %s", string(info)))
}
}
}
return resp, err
}
55 changes: 55 additions & 0 deletions backend/pkg/core/client/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package client

import (
"fmt"
"net/http"

"github.com/kyverno/policy-reporter-ui/pkg/core/utils"
)

type ClientOption = func(*Client) error

func WithBaseURL(url string) ClientOption {
return func(client *Client) error {
client.baseURL = url

return nil
}
}

func WithBaseAuth(auth BasicAuth) ClientOption {
return func(client *Client) error {
client.auth = &auth

return nil
}
}

func WithCertificate(path string) ClientOption {
return func(client *Client) error {
certs, err := utils.LoadCerts(path)
if err != nil {
return fmt.Errorf("with certificate failed: %w", err)
}

client.http.Transport.(*http.Transport).TLSClientConfig.RootCAs = certs

return nil
}
}

func WithSkipTLS() ClientOption {
return func(client *Client) error {
client.http.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true

return nil
}
}

func WithLogging() ClientOption {
return func(client *Client) error {
client.http.Transport = newLoggingRoundTripper(client.http.Transport)

return nil
}
}
10 changes: 3 additions & 7 deletions backend/pkg/proxy/proxy.go → backend/pkg/core/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ package proxy

import (
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"time"

"github.com/kyverno/policy-reporter-ui/pkg/core/utils"
"go.uber.org/zap"
)

Expand Down Expand Up @@ -49,16 +48,13 @@ func WithAuth(username, password string) DirectorOption {

func WithCertificate(certificatePath string) ProxyOption {
return func(proxy *httputil.ReverseProxy) {
caCert, err := os.ReadFile(certificatePath)
pool, err := utils.LoadCerts(certificatePath)
if err != nil {
zap.L().Error("failed to read certificate", zap.Error(err), zap.String("path", certificatePath))
return
}

caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

proxy.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool
proxy.Transport.(*http.Transport).TLSClientConfig.RootCAs = pool
}
}

Expand Down
18 changes: 18 additions & 0 deletions backend/pkg/core/utils/certs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package utils

import (
"crypto/x509"
"os"
)

func LoadCerts(path string) (*x509.CertPool, error) {
caCert, err := os.ReadFile(path)
if err != nil {
return nil, err
}

caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

return caCertPool, nil
}
Loading

0 comments on commit 05cc9e7

Please sign in to comment.