Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
x0ddf committed Feb 9, 2025
0 parents commit 7aaca82
Show file tree
Hide file tree
Showing 9 changed files with 447 additions and 0 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Build

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
cache: true

- name: Verify dependencies
run: go mod verify

- name: Build
run: go build -v ./...

- name: Run tests
run: go test -v ./...

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
54 changes: 54 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Release

on:
push:
tags: [ 'v*' ]

env:
PLATFORMS: "linux darwin windows"
ARCH: "amd64 arm64"
jobs:
docker:
name: Release docker image
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v4

- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea/
.vscode/
.DS_Store
40 changes: 40 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Build stage
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder

WORKDIR /app

# Install necessary build tools
RUN apk add --no-cache git

# Copy go mod files
COPY go.mod ./
COPY go.sum ./

# Download dependencies
RUN go mod download

# Copy source code
COPY . .

# Build arguments for multi-arch support
ARG TARGETARCH
ARG TARGETOS

# Build the application
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build \
-ldflags="-w -s" \
-o /whodidthat-controller

# Final stage
FROM gcr.io/distroless/static:nonroot

WORKDIR /

# Copy the binary from builder
COPY --from=builder /whodidthat-controller .

# Expose the port
EXPOSE 8443

# Container runs as nonroot (uid:65532, gid:65532) by default in distroless
ENTRYPOINT ["/whodidthat-controller"]
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# WhoDidThat Controller

A Kubernetes admission controller that automatically tracks resource creation and modification by adding user information labels.

## Overview

WhoDidThat Controller is a Kubernetes mutating admission webhook that adds audit labels to resources, tracking who created and last modified them. It adds the following labels:
- `audit.k8s.io/created-by`: The user who created the resource
- `audit.k8s.io/last-modified-by`: The user who last modified the resource

## Features

- Tracks resource creation and modification events
- Adds user information as Kubernetes labels
- Supports all Kubernetes resource types
- Multi-architecture support (AMD64 and ARM64)
- Minimal footprint using distroless container image

## Prerequisites

- Kubernetes cluster 1.16+
- kubectl configured to access your cluster
- cert-manager (recommended for TLS certificate management)

## Installation

### Using Helm (Recommended)

1. Add the repository:
```bash
helm repo add whodidthat https://x0ddf.github.io/whodidthat-controller
```

110 changes: 110 additions & 0 deletions controllers/admission_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package controllers

import (
"encoding/json"
"fmt"
"net/http"
"strings"

admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
)

const (
CreatedByLabel = "audit.k8s.io/created-by"
ModifiedByLabel = "audit.k8s.io/last-modified-by"
)

type AdmissionController struct {
decoder runtime.Decoder
}

func NewAdmissionController() *AdmissionController {
return &AdmissionController{
decoder: serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer(),
}
}

func (ac *AdmissionController) Handle(w http.ResponseWriter, r *http.Request) {
// Parse the AdmissionReview
admissionReview := &admissionv1.AdmissionReview{}
if err := json.NewDecoder(r.Body).Decode(admissionReview); err != nil {
http.Error(w, fmt.Sprintf("could not parse admission review: %v", err), http.StatusBadRequest)
return
}

// Get the requesting user
userName := ExtractUserMeta(admissionReview.Request)

// Create patch operations
var patches []map[string]interface{}

// Handle different operations
switch admissionReview.Request.Operation {
case admissionv1.Create:
patches = append(patches, map[string]interface{}{
"op": "add",
"path": "/metadata/labels/" + escapeJSONPointer(CreatedByLabel),
"value": userName,
})
case admissionv1.Update:
patches = append(patches, map[string]interface{}{
"op": "add",
"path": "/metadata/labels/" + escapeJSONPointer(ModifiedByLabel),
"value": userName,
})
}

// Create the patch bytes
patchBytes, err := json.Marshal(patches)
if err != nil {
http.Error(w, fmt.Sprintf("could not marshal patch: %v", err), http.StatusInternalServerError)
return
}

// Create the admission response
admissionResponse := &admissionv1.AdmissionResponse{
UID: admissionReview.Request.UID,
Allowed: true,
Patch: patchBytes,
PatchType: func() *admissionv1.PatchType {
pt := admissionv1.PatchTypeJSONPatch
return &pt
}(),
}

// Return the admission review
admissionReview.Response = admissionResponse
resp, err := json.Marshal(admissionReview)
if err != nil {
http.Error(w, fmt.Sprintf("could not marshal response: %v", err), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}

// escapeJSONPointer escapes / in label names for JSON Pointer compliance
func escapeJSONPointer(s string) string {
return strings.ReplaceAll(s, "/", "~1")
}

func ExtractUserMeta(request *admissionv1.AdmissionRequest) string {
username := ""
extraInfo := request.UserInfo.Extra
if v, ok := extraInfo["username"]; ok && len(v) > 0 {
username = v.String()
}
if v, ok := extraInfo["sessionName"]; ok && len(v) > 0 {
username = v.String()
}
if v, ok := extraInfo["arn"]; ok && len(v) > 0 {
username = v.String()
}
if len(username) == 0 {
username = request.UserInfo.Username
}
return username
}
30 changes: 30 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module github.com/x0ddf/whodidthat-controller

go 1.23.6

toolchain go1.23.6

require (
k8s.io/api v0.32.1
k8s.io/apimachinery v0.32.1
)

require (
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/text v0.22.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
Loading

0 comments on commit 7aaca82

Please sign in to comment.