internal/safety/safety.go (68 lines of code) (raw):
/*
Package safety provides a set of safety checks for exposing Kubernetes resources to the outside world.
Usage:
s := safety.New(ctx, in, out, safety.WithLogger(l))
// Read entries that have been scrubbed of sensitive information.
go func() {
// read entries from out
for e := range out {
// do something with e
}
}()
// Send entries to be scrubbed.
for _, e := range entries {
in <- e
}
*/
package safety
import (
"context"
"fmt"
"log/slog"
"github.com/Azure/tattler/data"
corev1 "k8s.io/api/core/v1"
)
// Secrets provide a set of safety checks for exposing Kubernetes resources to the outside world.
// It currently scrubs sensitive information from informers that have pods with containers that have
// environment variables with names that match a secret regular expression.
type Secrets struct {
in <-chan data.Entry
out chan data.Entry
log *slog.Logger
}
// Option is a functional option for the Secrets.
type Option func(*Secrets) error
// WithLogger sets the logger for the Secrets. Defaults to slog.Default().
func WithLogger(l *slog.Logger) Option {
return func(s *Secrets) error {
s.log = l
return nil
}
}
// New creates a new Secrets. The pipeline is ready once New() is called successfully.
// Closing in will close out.
func New(ctx context.Context, in <-chan data.Entry, out chan data.Entry, options ...Option) (*Secrets, error) {
if in == nil || out == nil {
panic("can't call Secrets.New() with a nil in or out channel")
}
s := &Secrets{
in: in,
out: out,
log: slog.Default(),
}
for _, o := range options {
if err := o(s); err != nil {
return nil, err
}
}
go s.run()
return s, nil
}
// run starts the Secrets processing.
func (s *Secrets) run() {
defer close(s.out)
for e := range s.in {
s.scrubber(e)
}
}
// scrubber scrubs sensitive information from an informer.
func (s *Secrets) scrubber(e data.Entry) error {
switch e.ObjectType() {
case data.OTPod:
p, err := e.Pod()
if err != nil {
return fmt.Errorf("safety.Secrets.informerRouter: error casting object to pod: %v", err)
}
s.scrubPod(p)
}
s.out <- e
return nil
}
// scrubPod scrubs sensitive information from a pod.
func (s *Secrets) scrubPod(p *corev1.Pod) {
spec := p.Spec
for _, cont := range spec.Containers {
s.scrubContainer(cont)
}
}
// This is the regular expression that is used to match environment variables that are sensitive.
// And the code that would be used to scrub the sensitive information.
// Commented out for now because we are going to scrub all values.
/*
var secretRE = regexp.MustCompile(`(?i)(token|pass|pwd|jwt|hash|secret|bearer|cred|secure|signing|cert|code|key)`)
if secretRE.MatchString(ev.Name) {
ev.Value = redacted
c.Env[i] = ev
}
*/
var redacted = "REDACTED"
// scrubContainer scrubs sensitive information from a container.
func (s *Secrets) scrubContainer(c corev1.Container) {
for i, ev := range c.Env {
ev.Value = redacted
c.Env[i] = ev
}
}