Skip to content

Commit

Permalink
Add boolen configuration to enable or disable request interception
Browse files Browse the repository at this point in the history
  • Loading branch information
kchiranjewee63 committed Jun 12, 2024
1 parent 1e05e60 commit ad1afc6
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 52 deletions.
1 change: 1 addition & 0 deletions kubernetes/tconfigd/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
enableTratInterception: "false"
12 changes: 8 additions & 4 deletions kubernetes/tconfigd/deploy.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
docker build -t tconfigd:latest -f ../../service/Dockerfile ../../service/

kubectl create namespace tratteria

kubectl create configmap config --from-file=config.yaml=config.yaml -n tratteria

cd ../../rules
chmod +x deploy-rules.sh
./deploy-rules.sh example-rules
cd ../kubernetes/tconfigd

kubectl apply -f service-account.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f tratteria-agent-injector-mutating-webhook.yaml

cd ../../rules
chmod +x deploy-rules.sh
./deploy-rules.sh example-rules
13 changes: 10 additions & 3 deletions kubernetes/tconfigd/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,34 @@ spec:
value: spiffe://tratteria.io/tratteria
image: tconfigd
name: tconfigd
args: ["/etc/tconfigd/config/config.yaml"]
imagePullPolicy: Never
ports:
- containerPort: 9060
protocol: TCP
- containerPort: 443
protocol: TCP
volumeMounts:
- name: config-volume
mountPath: "/etc/rules"
- name: trats-rules-volume
mountPath: "/etc/tconfigd/rules"
readOnly: true
- name: tconfigd-config-volume
mountPath: "/etc/tconfigd/config"
readOnly: true
- mountPath: /run/spire/sockets
name: spire-agent-socket
readOnly: true
restartPolicy: Always
volumes:
- name: config-volume
- name: trats-rules-volume
configMap:
name: trats-rules-config
items:
- key: "trats-rules.ndjson"
path: "trats-rules.ndjson"
- name: tconfigd-config-volume
configMap:
name: config
- name: spire-agent-socket
hostPath:
path: /run/spire/sockets
Expand Down
2 changes: 1 addition & 1 deletion service/api/pkg/rules/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func NewRules() *Rules {
}

func (r *Rules) Load() error {
traTs, generationRules, verificationRules, err := parse("/etc/rules/trats-rules.ndjson")
traTs, generationRules, verificationRules, err := parse("/etc/tconfigd/rules/trats-rules.ndjson")
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion service/api/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func Run() error {
ReadTimeout: 15 * time.Second,
}

logger.Info("Starting rules server on port 9060.")
logger.Info("Starting api server on port 9060.")

if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Error("Failed to start the api server", zap.Error(err))
Expand Down
17 changes: 16 additions & 1 deletion service/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"syscall"

"github.com/tratteria/tconfigd/api"
"github.com/tratteria/tconfigd/config"
"github.com/tratteria/tconfigd/webhook"
"github.com/tratteria/tconfigd/webhook/webhookconfig"
)

func main() {
Expand All @@ -17,6 +19,17 @@ func main() {

setupSignalHandler(cancel)

if len(os.Args) < 2 {
log.Fatalf("No configuration file provided. Please specify the configuration path as an argument when running the service.\nUsage: %s <config-path>", os.Args[0])
}

configPath := os.Args[1]

appConfig, err := config.GetAppConfig(configPath)
if err != nil {
log.Fatalf("Error reading configuration: %v", err)
}

go func() {
log.Println("Starting API server...")

Expand All @@ -28,7 +41,9 @@ func main() {
go func() {
log.Println("Starting Webhook server...")

if err := webhook.Run(); err != nil {
webhookConfig := webhookconfig.WebhookConfig{EnableTratInterception: bool(appConfig.EnableTratInterception)}

if err := webhook.Run(&webhookConfig); err != nil {
log.Fatalf("Webhook server failed: %v", err)
}
}()
Expand Down
121 changes: 121 additions & 0 deletions service/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package config

import (
"fmt"
"os"
"reflect"
"regexp"
"strconv"

"gopkg.in/yaml.v2"
)

type BoolFromString bool

func (b *BoolFromString) UnmarshalYAML(unmarshal func(interface{}) error) error {
var tmp interface{}
if err := unmarshal(&tmp); err != nil {
return err
}

switch value := tmp.(type) {
case bool:
*b = BoolFromString(value)
case string:
if matched, envVarName := extractEnvVarName(value); matched {
envValue, err := getEnvVarValue(envVarName)
if err != nil {
return err
}

boolVal, err := strconv.ParseBool(envValue)

if err != nil {
return fmt.Errorf("error parsing boolean from environment variable: %v", err)
}

*b = BoolFromString(boolVal)
} else {
boolVal, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("error parsing boolean from string: %v", err)
}

*b = BoolFromString(boolVal)
}
default:
return fmt.Errorf("invalid type for a bool variable, expected bool or string, got %T", tmp)
}

return nil
}

type AppConfig struct {
EnableTratInterception BoolFromString `yaml:"enableTratInterception"`
}

func GetAppConfig(configPath string) (*AppConfig, error) {
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}

var cfg AppConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to unmarshal YAML configuration: %w", err)
}

resolveEnvVariables(&cfg)

return &cfg, nil
}

func extractEnvVarName(s string) (bool, string) {
envVarRegex := regexp.MustCompile(`^\$\{([^}]+)\}$`)
matches := envVarRegex.FindStringSubmatch(s)

if len(matches) > 1 {
return true, matches[1]
}

return false, ""
}

func getEnvVarValue(envVarName string) (string, error) {
if envValue, exists := os.LookupEnv(envVarName); exists {
return envValue, nil
}

return "", fmt.Errorf("environment variable %s not set", envVarName)
}

func resolveEnvVariablesUtil(v reflect.Value) {
if v.Kind() == reflect.Ptr {
v = v.Elem()
}

for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if field.Kind() == reflect.String {
fieldValue := field.String()

if matched, envVarName := extractEnvVarName(fieldValue); matched {
envValue, err := getEnvVarValue(envVarName)
if err != nil {
panic(err.Error())
}

field.SetString(envValue)
}
} else if field.Kind() == reflect.Struct {
resolveEnvVariablesUtil(field)
} else if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
resolveEnvVariablesUtil(field.Elem())
}
}
}

func resolveEnvVariables(cfg *AppConfig) {
v := reflect.ValueOf(cfg)
resolveEnvVariablesUtil(v)
}
2 changes: 1 addition & 1 deletion service/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/spiffe/go-spiffe/v2 v2.2.0
go.uber.org/zap v1.27.0
google.golang.org/grpc v1.62.1
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.30.1
k8s.io/apimachinery v0.30.1
)
Expand All @@ -33,7 +34,6 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
Expand Down
27 changes: 15 additions & 12 deletions service/webhook/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"

"github.com/tratteria/tconfigd/webhook/pkg/util"
"github.com/tratteria/tconfigd/webhook/webhookconfig"

"go.uber.org/zap"

Expand All @@ -14,29 +15,31 @@ import (
)

type Handlers struct {
Logger *zap.Logger
config *webhookconfig.WebhookConfig
logger *zap.Logger
}

func NewHandlers(logger *zap.Logger) *Handlers {
func NewHandlers(config *webhookconfig.WebhookConfig, logger *zap.Logger) *Handlers {
return &Handlers{
Logger: logger,
config: config,
logger: logger,
}
}

func (h *Handlers) InjectTratteriaAgent(w http.ResponseWriter, r *http.Request) {
h.Logger.Info("Received Agent Injection Request")
h.logger.Info("Received Agent Injection Request")

var admissionReview admissionv1.AdmissionReview

if err := json.NewDecoder(r.Body).Decode(&admissionReview); err != nil {
h.Logger.Error("Failed to decode admission review", zap.Error(err))
h.logger.Error("Failed to decode admission review", zap.Error(err))
http.Error(w, "could not decode admission review", http.StatusBadRequest)

return
}

if admissionReview.Request == nil {
h.Logger.Error("Received an AdmissionReview with no Request")
h.logger.Error("Received an AdmissionReview with no Request")
http.Error(w, "received an AdmissionReview with no Request", http.StatusBadRequest)

return
Expand All @@ -49,23 +52,23 @@ func (h *Handlers) InjectTratteriaAgent(w http.ResponseWriter, r *http.Request)

var pod corev1.Pod
if err := json.Unmarshal(admissionReview.Request.Object.Raw, &pod); err != nil {
h.Logger.Error("Could not unmarshal raw object into pod", zap.Error(err))
h.logger.Error("Could not unmarshal raw object into pod", zap.Error(err))
admissionResponse.Result = &metav1.Status{
Message: err.Error(),
}
} else {
patchOps, err := util.CreatePodPatch(&pod)
patchOps, err := util.CreatePodPatch(&pod, h.config.EnableTratInterception)

if err != nil {
h.Logger.Error("Could not create patch for pod", zap.Error(err))
h.logger.Error("Could not create patch for pod", zap.Error(err))
admissionResponse.Result = &metav1.Status{
Message: err.Error(),
}
} else {
patchBytes, err := json.Marshal(patchOps)

if err != nil {
h.Logger.Error("Failed to marshal patch operations", zap.Error(err))
h.logger.Error("Failed to marshal patch operations", zap.Error(err))
admissionResponse.Result = &metav1.Status{
Message: err.Error(),
}
Expand All @@ -89,9 +92,9 @@ func (h *Handlers) InjectTratteriaAgent(w http.ResponseWriter, r *http.Request)
w.Header().Set("Content-Type", "application/json")

if err := json.NewEncoder(w).Encode(responseAdmissionReview); err != nil {
h.Logger.Error("Failed to write response", zap.Error(err))
h.logger.Error("Failed to write response", zap.Error(err))
http.Error(w, "failed to write response", http.StatusInternalServerError)
}

h.Logger.Info("Agent Injection Request Processed Successfully", zap.Any("patched-pod", responseAdmissionReview))
h.logger.Info("Agent Injection Request Processed Successfully", zap.Any("patched-pod", responseAdmissionReview))
}
Loading

0 comments on commit ad1afc6

Please sign in to comment.