Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build working again now, also modified for multi-architecture builds. #115

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
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
6 changes: 4 additions & 2 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# === Stage 1: Build and bundle the Vue.js app with Vue CLI 3 ====================================
# ================================================================================================
FROM node:16-alpine as vue-build
ARG TARGETARCH
ARG sourceDir="web/client"

WORKDIR /build
Expand All @@ -22,7 +23,8 @@ RUN npm run build
# ================================================================================================
# === Stage 2: Build Golang API server and host for Vue app ======================================
# ================================================================================================
FROM golang:1.17-alpine as go-build
FROM golang:1.23-alpine as go-build
ARG TARGETARCH
WORKDIR /build
ARG GO_PACKAGE="github.com/benc-uk/kubeview/cmd/server"
ARG VERSION="0.0.0"
Expand Down Expand Up @@ -61,4 +63,4 @@ COPY --from=go-build /build/server .
EXPOSE 8000

# That's it! Just run the server
CMD [ "./server"]
CMD [ "./server"]
8 changes: 4 additions & 4 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"path/filepath"
"strings"

"github.com/benc-uk/go-starter/pkg/envhelper"
"github.com/benc-uk/go-rest-api/pkg/env"

"github.com/gorilla/mux"
_ "github.com/joho/godotenv/autoload" // Autoloads .env file if it exists
Expand All @@ -38,8 +38,8 @@ func main() {
log.Printf("### Kubeview v%v starting...", version)

// Port to listen on, change the default as you see fit
serverPort := envhelper.GetEnvInt("PORT", 8000)
inCluster := envhelper.GetEnvBool("IN_CLUSTER", false)
serverPort := env.GetEnvInt("PORT", 8000)
inCluster := env.GetEnvBool("IN_CLUSTER", false)

log.Println("### Connecting to Kubernetes...")
var kubeConfig *rest.Config
Expand Down Expand Up @@ -80,7 +80,7 @@ func main() {
router.HandleFunc("/api/config", routeConfig)

// Serve the frontend Vue.js SPA
staticDirectory := envhelper.GetEnvString("STATIC_DIR", "./frontend")
staticDirectory := env.GetEnvString("STATIC_DIR", "./frontend")
spa := spaHandler{staticPath: staticDirectory, indexPath: "index.html"}
router.PathPrefix("/").Handler(spa)

Expand Down
131 changes: 92 additions & 39 deletions cmd/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import (
"log"
"net/http"
"os"
"regexp"
"runtime"
"strings"

"github.com/benc-uk/go-starter/pkg/envhelper"
"github.com/benc-uk/go-rest-api/pkg/env"
"github.com/gorilla/mux"

appsv1 "k8s.io/api/apps/v1"
Expand Down Expand Up @@ -45,9 +46,63 @@ type scrapeData struct {
Secrets []apiv1.Secret `json:"secrets"`
}

//
// Redact any certificate data from the input byte slice
func redactCertificates(data []byte) []byte {
certRegex := regexp.MustCompile(`(?i)-----+BEGIN\s+CERTIFICATE-----+[^\-]+-----+END\s+CERTIFICATE-----+`)
return certRegex.ReplaceAll(data, []byte("__CERTIFICATE REDACTED__"))
}

// Redact certificates from any JSON-like structure recursively
func redactCertificatesInJSON(data interface{}) interface{} {
switch v := data.(type) {
case map[string]interface{}:
for key, val := range v {
v[key] = redactCertificatesInJSON(val)
}
return v
case []interface{}:
for i, val := range v {
v[i] = redactCertificatesInJSON(val)
}
return v
case string:
return string(redactCertificates([]byte(v)))
default:
return data
}
}

func redactSecrets(secrets []apiv1.Secret) []apiv1.Secret {
for i, secret := range secrets {
// Redact from secret data
for key, value := range secret.Data {
secret.Data[key] = redactCertificates(value)
}

// Redact from annotations, including kubectl.kubernetes.io/last-applied-configuration
for key, value := range secret.Annotations {
if key == "kubectl.kubernetes.io/last-applied-configuration" {
// Redact sensitive data within the last-applied-configuration JSON
secret.Annotations[key] = string(redactCertificates([]byte(value)))
} else {
// Redact certificates from other annotations as well
secret.Annotations[key] = string(redactCertificates([]byte(value)))
}
}

// Handle StringData field as well
for key, value := range secret.StringData {
redactedValue := redactCertificates([]byte(value))
secret.StringData[key] = string(redactedValue)
}

// Reassign the redacted secret back to the slice
secrets[i] = secret
}
return secrets
}

// Simple health check endpoint, returns 204 when healthy
//
func routeHealthCheck(resp http.ResponseWriter, req *http.Request) {
if healthy {
resp.WriteHeader(http.StatusNoContent)
Expand All @@ -56,9 +111,7 @@ func routeHealthCheck(resp http.ResponseWriter, req *http.Request) {
resp.WriteHeader(http.StatusServiceUnavailable)
}

//
// Return status information data
//
func routeStatus(resp http.ResponseWriter, req *http.Request) {
type status struct {
Healthy bool `json:"healthy"`
Expand Down Expand Up @@ -103,9 +156,7 @@ func routeStatus(resp http.ResponseWriter, req *http.Request) {
}
}

//
// Return list of all namespaces in cluster
//
func routeGetNamespaces(resp http.ResponseWriter, req *http.Request) {
ctx := context.Background()
namespaces, err := clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
Expand All @@ -122,96 +173,94 @@ func routeGetNamespaces(resp http.ResponseWriter, req *http.Request) {
}
}

//
// Return aggregated data from loads of different Kubernetes object types
//
func routeScrapeData(resp http.ResponseWriter, req *http.Request) {
params := mux.Vars(req)
namespace := params["ns"]

ctx := context.Background()

// Fetch Kubernetes resources
pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

services, err := clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

endpoints, err := clientset.CoreV1().Endpoints(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

pvs, err := clientset.CoreV1().PersistentVolumes().List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

pvcs, err := clientset.CoreV1().PersistentVolumeClaims(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

configmaps, err := clientset.CoreV1().ConfigMaps(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

secrets, err := clientset.CoreV1().Secrets(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

deployments, err := clientset.AppsV1().Deployments(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

daemonsets, err := clientset.AppsV1().DaemonSets(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

replicasets, err := clientset.AppsV1().ReplicaSets(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

statefulsets, err := clientset.AppsV1().StatefulSets(namespace).List(ctx, metav1.ListOptions{})
//t := statefulsets.Items
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}

ingresses, err := clientset.NetworkingV1().Ingresses(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
log.Println("### Kubernetes API error", err.Error())
log.Println("### Kubernetes API error:", err.Error())
http.Error(resp, err.Error(), http.StatusInternalServerError)
return
}
Expand All @@ -221,20 +270,21 @@ func routeScrapeData(resp http.ResponseWriter, req *http.Request) {
return !strings.HasPrefix(v.ObjectMeta.Name, "sh.helm.release")
})

// Obfuscate & remove secret values
for _, secret := range secrets.Items {
// Inside 'last-applied-configuration'
if secret.ObjectMeta.Annotations["kubectl.kubernetes.io/last-applied-configuration"] != "" {
secret.ObjectMeta.Annotations["kubectl.kubernetes.io/last-applied-configuration"] = "__VALUE REDACTED__"
// Redact sensitive data within secrets, including in kubectl.kubernetes.io/last-applied-configuration
secrets.Items = redactSecrets(secrets.Items)

// Redact any certificate data from ConfigMaps
for _, configmap := range configmaps.Items {
for key, value := range configmap.Data {
configmap.Data[key] = string(redactCertificates([]byte(value)))
}

// And the data values of course
for key := range secret.Data {
secret.Data[key] = []byte("__VALUE REDACTED__")
for key, value := range configmap.BinaryData {
configmap.BinaryData[key] = redactCertificates(value)
}
}

// Dump of results
// Dump of results into the scrapeData struct
scrapeResult := scrapeData{
Pods: pods.Items,
Services: services.Items,
Expand All @@ -250,20 +300,25 @@ func routeScrapeData(resp http.ResponseWriter, req *http.Request) {
Secrets: secrets.Items,
}

scrapeResultJSON, _ := json.Marshal(scrapeResult)
// Marshal the results into JSON
scrapeResultJSON, err := json.Marshal(scrapeResult)
if err != nil {
log.Println("### Failed to marshal scrape result: ", err)
http.Error(resp, "Internal Server Error", http.StatusInternalServerError)
return
}

// Set headers and write response
resp.Header().Set("Access-Control-Allow-Origin", "*")
resp.Header().Add("Content-Type", "application/json")
_, err = resp.Write([]byte(scrapeResultJSON))
if err != nil {
if _, err := resp.Write(scrapeResultJSON); err != nil {
log.Println("Unable to write")
}
}

//
// Simple config endpoint, returns NAMESPACE_SCOPE var to front end
//
func routeConfig(resp http.ResponseWriter, req *http.Request) {
nsScope := envhelper.GetEnvString("NAMESPACE_SCOPE", "*")
nsScope := env.GetEnvString("NAMESPACE_SCOPE", "*")
conf := Config{NamespaceScope: nsScope}

configJSON, _ := json.Marshal(conf)
Expand All @@ -275,9 +330,7 @@ func routeConfig(resp http.ResponseWriter, req *http.Request) {
}
}

//
// Filter a slice of Secrets
//
func filterSecrets(secretList []apiv1.Secret, f func(apiv1.Secret) bool) []apiv1.Secret {
newSlice := make([]apiv1.Secret, 0)
for _, secret := range secretList {
Expand Down
14 changes: 8 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
module github.com/benc-uk/kubeview

go 1.17
go 1.23

toolchain go1.23.0

require (
github.com/benc-uk/go-starter v1.0.0
github.com/gorilla/mux v1.8.0
github.com/joho/godotenv v1.4.0
github.com/joho/godotenv v1.5.1
k8s.io/api v0.22.3
k8s.io/apimachinery v0.22.3
k8s.io/client-go v0.22.3
)

require (
github.com/benc-uk/go-rest-api v1.0.11 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
Expand All @@ -26,12 +28,12 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.26.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
Expand Down
Loading