From 584cd1804355a1b44ed52dacb65bf05081a86564 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 19:49:30 +0000 Subject: [PATCH 01/27] feat: add authorization webhook --- .devcontainer/devcontainer.json | 44 ++++ .github/workflows/build.yaml | 50 ++++ Dockerfile | 21 ++ go.mod | 85 +++++++ go.sum | 235 ++++++++++++++++++ internal/authorization-webhook/cmd/serve.go | 157 ++++++++++++ internal/authorization-webhook/cmd/webhook.go | 16 ++ .../iam/core_control_plane_authorizer.go | 56 +++++ .../iam/project_control_plane_authorizer.go | 68 +++++ .../internal/webhook/http.go | 132 ++++++++++ .../internal/webhook/response.go | 43 ++++ .../internal/webhook/webhook.go | 86 +++++++ internal/cmd/root.go | 19 ++ main.go | 15 ++ 14 files changed, 1027 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/workflows/build.yaml create mode 100644 Dockerfile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/authorization-webhook/cmd/serve.go create mode 100644 internal/authorization-webhook/cmd/webhook.go create mode 100644 internal/authorization-webhook/internal/iam/core_control_plane_authorizer.go create mode 100644 internal/authorization-webhook/internal/iam/project_control_plane_authorizer.go create mode 100644 internal/authorization-webhook/internal/webhook/http.go create mode 100644 internal/authorization-webhook/internal/webhook/response.go create mode 100644 internal/authorization-webhook/internal/webhook/webhook.go create mode 100644 internal/cmd/root.go create mode 100644 main.go diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..16eb98f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,44 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/go +{ + "name": "Go", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/go:1-1.23-bookworm", + "features": { + "ghcr.io/devcontainers/features/common-utils": { + "installOhMyZsh": true, + "configureZshAsDefaultShell": true, + "installOhMyZshConfig": true, + "installZsh": true, + "upgradePackages": true + }, + "ghcr.io/devcontainers/features/docker-in-docker": {}, + "ghcr.io/dhoeric/features/act": {}, + }, + "customizations": { + "vscode": { + "extensions": [ + "patbenatar.advanced-new-file", + "stkb.rewrap", + "github.vscode-github-actions", + "yzhang.markdown-all-in-one" + ], + "settings": { + "rewrap.autoWrap.enabled": true + }, + "editor.tabSize": 2 + } + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "go version", + + // Configure tool-specific properties. + // "customizations": {}, +} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..bbcc8cf --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,50 @@ +name: Build and Publish Docker Image + +on: + push: {} + # branches: + # - main + pull_request: + +jobs: + build-and-push: + permissions: + contents: read + packages: write + attestations: write + id-token: write + + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3.3.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5.5.1 + with: + images: ghcr.io/datum-cloud/datum + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6.7.0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e592d1d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# Use the official Go image as a build stage +FROM golang:1.23 AS builder + +# Set the working directory inside the container +WORKDIR /app + +# Copy go.mod and go.sum files and download dependencies +COPY go.mod go.sum ./ +RUN go mod download + +# Copy the rest of the application source code +COPY . . + +# Build the application +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o datum . + +# Use a minimal image for the final container +FROM gcr.io/distroless/static +WORKDIR /app +COPY --from=builder /app/datum . +ENTRYPOINT ["/app/datum"] diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..231db1a --- /dev/null +++ b/go.mod @@ -0,0 +1,85 @@ +module go.datumapis.com/datum + +go 1.23.1 + +require ( + buf.build/gen/go/datum-cloud/iam/grpc/go v1.5.1-20241213191836-af2fcd47dc01.1 + buf.build/gen/go/datum-cloud/iam/protocolbuffers/go v1.34.2-20241213191836-af2fcd47dc01.2 + github.com/spf13/cobra v1.8.1 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 + go.opentelemetry.io/otel/sdk v1.32.0 + go.opentelemetry.io/otel/trace v1.32.0 + google.golang.org/grpc v1.67.1 + google.golang.org/protobuf v1.35.1 + k8s.io/api v0.31.1 + k8s.io/apimachinery v0.31.1 + k8s.io/apiserver v0.31.1 + sigs.k8s.io/controller-runtime v0.19.3 +) + +require ( + cloud.google.com/go/longrunning v0.6.3 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.7.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/client-go v0.31.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6fb8481 --- /dev/null +++ b/go.sum @@ -0,0 +1,235 @@ +buf.build/gen/go/datum-cloud/iam/grpc/go v1.5.1-20241213191836-af2fcd47dc01.1 h1:Nh8ywHVEPsvU0XGJEB6Afz9bSbj9TbT1CAmTIrLk9sM= +buf.build/gen/go/datum-cloud/iam/grpc/go v1.5.1-20241213191836-af2fcd47dc01.1/go.mod h1:Wo027svrZbeZo2telt44K1SsxHWbJOyRgdfp1ynziFI= +buf.build/gen/go/datum-cloud/iam/protocolbuffers/go v1.34.2-20241213191836-af2fcd47dc01.2 h1:fE6jpZrtCziMMFcsbe605i65OP+PuT3zndDc4vDqLbs= +buf.build/gen/go/datum-cloud/iam/protocolbuffers/go v1.34.2-20241213191836-af2fcd47dc01.2/go.mod h1:53+MZA8zSmHKgAnLMCSPlUYz18mLwFr0DeGsTDzCtRQ= +cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng= +cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= +k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= +k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= +k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= +k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= +k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM= +k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= +k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= +sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/authorization-webhook/cmd/serve.go b/internal/authorization-webhook/cmd/serve.go new file mode 100644 index 0000000..3e950ed --- /dev/null +++ b/internal/authorization-webhook/cmd/serve.go @@ -0,0 +1,157 @@ +package cmd + +import ( + "context" + "fmt" + + "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" + "go.datumapis.com/datum/internal/authorization-webhook/internal/iam" + authwebhook "go.datumapis.com/datum/internal/authorization-webhook/internal/webhook" + + "github.com/spf13/cobra" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.20.0" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "k8s.io/api/authentication/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +func serveCommand() *cobra.Command { + var iamEndpoint, tracingEndpoint, certDir, certFile, keyFile string + var iamInsecure, tracingInsecure bool + + var serveCmd = &cobra.Command{ + Use: "serve", + Short: "Run the Authorization Webhook API server", + RunE: func(cmd *cobra.Command, args []string) error { + if iamEndpoint == "" { + return fmt.Errorf("`--iam-endpoint` is required") + } + + if tracingEndpoint != "" { + opts := []otlptracegrpc.Option{ + otlptracegrpc.WithEndpoint(tracingEndpoint), + } + if tracingInsecure { + opts = append(opts, otlptracegrpc.WithInsecure()) + } + + exporter, err := otlptrace.New(cmd.Context(), otlptracegrpc.NewClient(opts...)) + if err != nil { + return err + } + + otel.SetTracerProvider(trace.NewTracerProvider( + trace.WithSampler(trace.AlwaysSample()), + trace.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName("authorization-webhook.datumapis.com"), + )), + trace.WithBatcher(exporter), + )) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + )) + } + + dialOptions := []grpc.DialOption{ + grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + grpc.WithChainUnaryInterceptor( + func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + logger := log.Log.WithName("grpc_client") + logger.Info(method, ".request: ", protojson.Format(req.(proto.Message))) + err := invoker(ctx, method, req, reply, cc, opts...) + logger.Info(method, ".response: ", protojson.Format(reply.(proto.Message))) + return err + }, + ), + } + + if iamInsecure { + dialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials())) + } else { + dialOptions = append(dialOptions, grpc.WithTransportCredentials(credentials.NewTLS(nil))) + } + + iamConnection, err := grpc.NewClient(iamEndpoint, dialOptions...) + if err != nil { + return fmt.Errorf("failed to create new IAM client: %w", err) + } + defer iamConnection.Close() + + log.SetLogger(zap.New(zap.JSONEncoder())) + + iamClient := iamv1alphagrpc.NewAccessCheckClient(iamConnection) + + entryLog := log.Log.WithName("entrypoint") + + restConfig, err := config.GetConfig() + if err != nil { + return fmt.Errorf("failed to get rest config: %s", err) + } + + runtimeScheme := runtime.NewScheme() + v1beta1.AddToScheme(runtimeScheme) + + // Setup a Manager + entryLog.Info("setting up manager") + mgr, err := manager.New(restConfig, manager.Options{ + Scheme: runtimeScheme, + Metrics: server.Options{ + BindAddress: ":8999", + }, + WebhookServer: webhook.NewServer(webhook.Options{ + CertDir: certDir, + CertName: certFile, + KeyName: keyFile, + }), + }) + if err != nil { + return fmt.Errorf("failed to setup manager: %s", err) + } + + // Setup webhooks + entryLog.Info("setting up webhook server") + hookServer := mgr.GetWebhookServer() + + entryLog.Info("registering webhooks to the webhook server") + + hookServer.Register("/project/v1alpha/{project...}", authwebhook.NewAuthorizerWebhook(&iam.ProjectControlPlaneAuthorizer{ + IAMClient: iamClient, + })) + hookServer.Register("/core/v1alpha/webhook", authwebhook.NewAuthorizerWebhook(&iam.CoreControlPlaneAuthorizer{ + IAMClient: iamClient, + })) + + return mgr.Start(context.Background()) + }, + } + + serveCmd.Flags().StringVar(&iamEndpoint, "iam-endpoint", "", "Endpoint to use for connecting to the datum gRPC API endpoint") + serveCmd.Flags().BoolVar(&iamInsecure, "iam-endpoint-insecure", false, "Whether the use an insecure connection when export") + + serveCmd.Flags().StringVar(&tracingEndpoint, "tracing-endpoint", "", "Endpoint to send OpenTelemetry traces too. Tracing is disabled if not provided.") + serveCmd.Flags().BoolVar(&tracingInsecure, "tracing-insecure", false, "Whether the use an insecure connection when exporting traces") + serveCmd.Flags().StringVar(&certDir, "cert-dir", "", "Directory that contains the TLS certs to use for serving the webhook") + serveCmd.Flags().StringVar(&certFile, "cert-file", "", "Filename in the directory that contains the TLS cert") + serveCmd.Flags().StringVar(&keyFile, "key-file", "", "Filename in the directory that contains the TLS private key") + + return serveCmd +} diff --git a/internal/authorization-webhook/cmd/webhook.go b/internal/authorization-webhook/cmd/webhook.go new file mode 100644 index 0000000..4031c6a --- /dev/null +++ b/internal/authorization-webhook/cmd/webhook.go @@ -0,0 +1,16 @@ +package cmd + +import "github.com/spf13/cobra" + +func Webhook() *cobra.Command { + cmd := &cobra.Command{ + Use: "authorization-webhook", + Short: "An authorization webhook backed by the Datum IAM service", + } + + cmd.AddCommand( + serveCommand(), + ) + + return cmd +} diff --git a/internal/authorization-webhook/internal/iam/core_control_plane_authorizer.go b/internal/authorization-webhook/internal/iam/core_control_plane_authorizer.go new file mode 100644 index 0000000..db172f7 --- /dev/null +++ b/internal/authorization-webhook/internal/iam/core_control_plane_authorizer.go @@ -0,0 +1,56 @@ +package iam + +import ( + "context" + "fmt" + "log/slog" + + "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" + iampb "buf.build/gen/go/datum-cloud/iam/protocolbuffers/go/datum/iam/v1alpha" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +var _ authorizer.Authorizer = &CoreControlPlaneAuthorizer{} + +type CoreControlPlaneAuthorizer struct { + IAMClient iamv1alphagrpc.AccessCheckClient +} + +// Authorize implements authorizer.Authorizer. +func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) { + req := &iampb.CheckAccessRequest{ + Resource: fmt.Sprintf("resourcemanager.datumapis.com/%s/%s", attributes.GetResource(), attributes.GetName()), + Subject: "user:" + attributes.GetUser().GetName(), + Permission: attributes.GetVerb(), + } + ctx, span := otel.Tracer("go.datum.net/k8s-authz-webhook").Start(ctx, "datum.k8s-authz-webhook.global.Authorize", trace.WithAttributes( + attribute.String("subject", req.Subject), + attribute.String("resource", req.Resource), + attribute.String("permission", req.Permission), + )) + defer span.End() + + if attributes.GetAPIGroup() != "resourcemanager.datumapis.com" { + slog.DebugContext(ctx, "No opinion on auth webhook request since API Group is not managed by webhook", slog.String("api_group", attributes.GetAPIGroup())) + return authorizer.DecisionNoOpinion, "", nil + } + + resp, err := o.IAMClient.CheckAccess(ctx, req) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + slog.ErrorContext(ctx, "failed to check subject access in IAM system", slog.String("error", err.Error())) + return authorizer.DecisionNoOpinion, "", err + } + span.SetAttributes(attribute.Bool("allowed", resp.GetAllowed())) + + if resp.GetAllowed() { + slog.DebugContext(ctx, "subject was granted access through IAM service") + return authorizer.DecisionAllow, "", nil + } + + return authorizer.DecisionDeny, "", nil +} diff --git a/internal/authorization-webhook/internal/iam/project_control_plane_authorizer.go b/internal/authorization-webhook/internal/iam/project_control_plane_authorizer.go new file mode 100644 index 0000000..27bc09b --- /dev/null +++ b/internal/authorization-webhook/internal/iam/project_control_plane_authorizer.go @@ -0,0 +1,68 @@ +package iam + +import ( + "context" + "fmt" + "log/slog" + + "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" + iampb "buf.build/gen/go/datum-cloud/iam/protocolbuffers/go/datum/iam/v1alpha" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +var _ authorizer.Authorizer = &ProjectControlPlaneAuthorizer{} + +type ProjectControlPlaneAuthorizer struct { + IAMClient iamv1alphagrpc.AccessCheckClient +} + +// Authorize implements authorizer.Authorizer. +func (o *ProjectControlPlaneAuthorizer) Authorize( + ctx context.Context, attributes authorizer.Attributes, +) (authorizer.Decision, string, error) { + + ctx, span := otel.Tracer("go.datum.net/k8s-authz-webhook").Start(ctx, "datum.k8s-authz-webhook.Authorize", trace.WithAttributes( + attribute.String("subject", attributes.GetUser().GetName()), + )) + defer span.End() + + projectNameContext := ctx.Value("resourcemanager.datumapis.com/project-name") + projectName, ok := projectNameContext.(string) + if !ok { + span.SetStatus(codes.Error, "no project ID present in webhook request") + slog.WarnContext(ctx, "no project name was present in the webhook authorize request") + return authorizer.DecisionNoOpinion, "", nil + } + + resourceURL := "resourcemanager.datumapis.com/" + projectName + permissionName := fmt.Sprintf("%s/%s.%s", attributes.GetAPIGroup(), attributes.GetResource(), attributes.GetVerb()) + + span.SetAttributes( + attribute.String("resource", resourceURL), + attribute.String("permission", permissionName), + ) + + resp, err := o.IAMClient.CheckAccess(ctx, &iampb.CheckAccessRequest{ + Resource: resourceURL, + Subject: "user:" + attributes.GetUser().GetName(), + Permission: permissionName, + }) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + slog.ErrorContext(ctx, "failed to check subject access in IAM system", slog.String("error", err.Error())) + return authorizer.DecisionNoOpinion, "", err + } + span.SetAttributes(attribute.Bool("allowed", resp.GetAllowed())) + + if resp.GetAllowed() { + slog.DebugContext(ctx, "subject was granted access through IAM service") + return authorizer.DecisionAllow, "", nil + } + + return authorizer.DecisionDeny, "", nil +} diff --git a/internal/authorization-webhook/internal/webhook/http.go b/internal/authorization-webhook/internal/webhook/http.go new file mode 100644 index 0000000..1e40a51 --- /dev/null +++ b/internal/authorization-webhook/internal/webhook/http.go @@ -0,0 +1,132 @@ +package webhook + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "strings" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + authorizationv1 "k8s.io/api/authorization/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var authorizationScheme = runtime.NewScheme() +var authorizationCodecs = serializer.NewCodecFactory(authorizationScheme) + +func init() { + utilruntime.Must(authorizationv1.AddToScheme(authorizationScheme)) +} + +// Request defines the input for an authorization handler. +// It contains information to identify the object in +// question (group, version, kind, resource, subresource, +// name, namespace), as well as the operation in question +// (e.g. Get, Create, etc), and the object itself. +type Request struct { + authorizationv1.SubjectAccessReview +} + +// Response is the output of an authorization handler. +// It contains a response indicating if a given +// operation is allowed. +type Response struct { + authorizationv1.SubjectAccessReview +} + +// HandlerFunc implements Handler interface using a single function. +type HandlerFunc func(context.Context, Request) Response + +// Handler can handle an TokenReview. +type Handler interface { + // Handle yields a response to an TokenReview. + // + // The supplied context is extracted from the received http.Request, allowing wrapping + // http.Handlers to inject values into and control cancelation of downstream request processing. + Handle(context.Context, Request) Response +} + +var _ Handler = HandlerFunc(nil) + +// Handle process the SubjectAccessReview by invoking the underlying function. +func (f HandlerFunc) Handle(ctx context.Context, req Request) Response { + return f(ctx, req) +} + +var _ http.Handler = &Webhook{} + +// ServeHTTP implements http.Handler. +func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var body []byte + var err error + + ctx, span := otel.Tracer("go.datum.net/k8s-authz-webhook").Start(r.Context(), "datum.k8s-authz-webhook.HandleRequest") + defer span.End() + + if wh.WithContextFunc != nil { + ctx = wh.WithContextFunc(ctx, r) + } + + var reviewResponse Response + if r.Body == nil { + err = errors.New("request body is empty") + reviewResponse = Errored(err) + wh.writeResponse(w, nil, reviewResponse) + return + } + defer r.Body.Close() + + if body, err = io.ReadAll(r.Body); err != nil { + span.SetStatus(codes.Error, err.Error()) + reviewResponse = Errored(err) + wh.writeResponse(w, nil, reviewResponse) + return + } + + // verify the content type is accurate + if contentType := r.Header.Get("Content-Type"); contentType != "application/json" { + reviewResponse = Errored(fmt.Errorf("contentType=%s, expected application/json", contentType)) + span.SetStatus(codes.Error, "invalid content-type header provided") + wh.writeResponse(w, nil, reviewResponse) + return + } + + req := Request{} + _, _, err = authorizationCodecs.UniversalDeserializer().Decode(body, nil, &req.SubjectAccessReview) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + reviewResponse = Errored(err) + wh.writeResponse(w, &req, reviewResponse) + return + } + + slog.InfoContext(ctx, "received SubjectAccessReview webhook request", slog.Any("subject_access_review", req)) + + if projectName := strings.TrimPrefix(r.RequestURI, "/v1alpha/"); strings.HasPrefix(projectName, "projects/") { + span.SetAttributes(attribute.String("project", projectName)) + ctx = context.WithValue(ctx, "resourcemanager.datumapis.com/project-name", projectName) + } + + reviewResponse = wh.Handle(ctx, req) + wh.writeResponse(w, &req, reviewResponse) +} + +// writeTokenResponse writes response resp to w. req is optional (can be nil) and adds +// context for the logger. +func (wh *Webhook) writeResponse(w io.Writer, req *Request, resp Response) { + _ = req + + resp.SetGroupVersionKind(authorizationv1.SchemeGroupVersion.WithKind("SubjectAccessReview")) + + if err := json.NewEncoder(w).Encode(resp.SubjectAccessReview); err != nil { + panic(err) + } +} diff --git a/internal/authorization-webhook/internal/webhook/response.go b/internal/authorization-webhook/internal/webhook/response.go new file mode 100644 index 0000000..3079eea --- /dev/null +++ b/internal/authorization-webhook/internal/webhook/response.go @@ -0,0 +1,43 @@ +// This file is inspired by https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/webhook/authentication/response.go +package webhook + +import ( + authorizationv1 "k8s.io/api/authorization/v1" +) + +// Allowed constructs a response indicating that the given user is authorized +// to perform the given action. The reason parameter is optional. +func Allowed(reason string) Response { + return AuthorizationResponse(true, false, reason, "") +} + +// NoOpinion constructs a response indicating that the authorizer has no rule +// allowing the request, but also no rule specifically denying the request +func NoOpinion() Response { + return AuthorizationResponse(false, false, "", "") +} + +// Denied constructs a response indicating that the given user is denied +// to perform the given action. The reason parameter is optional. +func Denied(reason string) Response { + return AuthorizationResponse(false, true, reason, "") +} + +// Errored creates a new Response for error-handling a request. +func Errored(err error) Response { + return AuthorizationResponse(false, false, "", err.Error()) +} + +// AuthorizationResponse returns a response an authorization request. +func AuthorizationResponse(allowed, denied bool, reason, evaluationError string) Response { + return Response{ + authorizationv1.SubjectAccessReview{ + Status: authorizationv1.SubjectAccessReviewStatus{ + Allowed: allowed, + Denied: denied, + Reason: reason, + EvaluationError: evaluationError, + }, + }, + } +} diff --git a/internal/authorization-webhook/internal/webhook/webhook.go b/internal/authorization-webhook/internal/webhook/webhook.go new file mode 100644 index 0000000..0e55388 --- /dev/null +++ b/internal/authorization-webhook/internal/webhook/webhook.go @@ -0,0 +1,86 @@ +package webhook + +import ( + "context" + "net/http" + + authorizationv1 "k8s.io/api/authorization/v1" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { + return &Webhook{ + Handler: HandlerFunc(func(ctx context.Context, r Request) Response { + if r.Spec.ResourceAttributes != nil && r.Spec.NonResourceAttributes != nil { + return Denied("must specify oneof resource or non-resource attributes, not both") + } + + attrs := authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: r.Spec.User, + UID: r.Spec.UID, + Groups: r.Spec.Groups, + }, + } + + if resourceAttrs := r.Spec.ResourceAttributes; resourceAttrs != nil { + attrs.Verb = resourceAttrs.Verb + attrs.Namespace = resourceAttrs.Namespace + attrs.APIGroup = resourceAttrs.Group + attrs.APIVersion = resourceAttrs.Version + attrs.Resource = resourceAttrs.Resource + attrs.Subresource = resourceAttrs.Subresource + attrs.Name = resourceAttrs.Name + attrs.ResourceRequest = true + } + + if nonResourceAttrs := r.Spec.NonResourceAttributes; nonResourceAttrs != nil { + attrs.Verb = nonResourceAttrs.Verb + attrs.Path = nonResourceAttrs.Path + attrs.ResourceRequest = false + } + + decision, reason, err := authzer.Authorize( + ctx, + attrs, + ) + if err != nil { + return Errored(err) + } + + status := authorizationv1.SubjectAccessReviewStatus{ + Reason: reason, + } + + switch decision { + case authorizer.DecisionAllow: + status.Allowed = true + case authorizer.DecisionDeny: + status.Denied = true + } + + return Response{ + SubjectAccessReview: authorizationv1.SubjectAccessReview{ + Status: status, + }, + } + }), + } +} + +// Webhook represents each individual webhook. +type Webhook struct { + // Handler actually processes an authorization request returning whether it was authorized + Handler Handler + + // WithContextFunc will allow you to take the http.Request.Context() and + // add any additional information such as passing the request path or + // headers thus allowing you to read them from within the handler + WithContextFunc func(context.Context, *http.Request) context.Context +} + +// Handle processes SubjectAccessReview. +func (wh *Webhook) Handle(ctx context.Context, req Request) Response { + return wh.Handler.Handle(ctx, req) +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go new file mode 100644 index 0000000..bdf53ce --- /dev/null +++ b/internal/cmd/root.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "go.datumapis.com/datum/internal/authorization-webhook/cmd" +) + +var webhook = &cobra.Command{ + Use: "datum", + Short: "Datum Cloud", +} + +func init() { + webhook.AddCommand(cmd.Webhook()) +} + +func Execute() error { + return webhook.Execute() +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..e7ac067 --- /dev/null +++ b/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "os" + + "go.datumapis.com/datum/internal/cmd" +) + +func main() { + if err := cmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} From efcb04a56bb493c250479183e0d077cbdb1bc538 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 20:17:10 +0000 Subject: [PATCH 02/27] chore: only run build on push to master --- .github/workflows/build.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bbcc8cf..53c91aa 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,9 +1,9 @@ name: Build and Publish Docker Image on: - push: {} - # branches: - # - main + push: + branches: + - main pull_request: jobs: From 8e3d1ecf33a3b903251d2c411363402a4a043225 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 20:34:47 +0000 Subject: [PATCH 03/27] restructure repo --- .github/workflows/build.yaml | 23 +++++++++++++++---- .../datum-authorization-webhook/Dockerfile | 6 ++--- .../iam/core_control_plane_authorizer.go | 0 .../iam/project_control_plane_authorizer.go | 0 .../app}/internal/webhook/http.go | 0 .../app}/internal/webhook/response.go | 0 .../app}/internal/webhook/webhook.go | 0 .../datum-authorization-webhook/app}/serve.go | 6 ++--- .../app}/webhook.go | 4 ++-- cmd/datum-authorization-webhook/webhook.go | 15 ++++++++++++ internal/cmd/root.go | 19 --------------- main.go | 15 ------------ 12 files changed, 42 insertions(+), 46 deletions(-) rename Dockerfile => cmd/datum-authorization-webhook/Dockerfile (74%) rename {internal/authorization-webhook => cmd/datum-authorization-webhook/app}/internal/iam/core_control_plane_authorizer.go (100%) rename {internal/authorization-webhook => cmd/datum-authorization-webhook/app}/internal/iam/project_control_plane_authorizer.go (100%) rename {internal/authorization-webhook => cmd/datum-authorization-webhook/app}/internal/webhook/http.go (100%) rename {internal/authorization-webhook => cmd/datum-authorization-webhook/app}/internal/webhook/response.go (100%) rename {internal/authorization-webhook => cmd/datum-authorization-webhook/app}/internal/webhook/webhook.go (100%) rename {internal/authorization-webhook/cmd => cmd/datum-authorization-webhook/app}/serve.go (96%) rename {internal/authorization-webhook/cmd => cmd/datum-authorization-webhook/app}/webhook.go (82%) create mode 100644 cmd/datum-authorization-webhook/webhook.go delete mode 100644 internal/cmd/root.go delete mode 100644 main.go diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 53c91aa..648dbf9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,8 +2,8 @@ name: Build and Publish Docker Image on: push: - branches: - - main + # branches: + # - main pull_request: jobs: @@ -15,6 +15,13 @@ jobs: id-token: write runs-on: ubuntu-latest + + # Define the services that should be built. + strategy: + matrix: + service: + - datum-authorization-webhook + steps: - name: Checkout repository uses: actions/checkout@v3 @@ -30,7 +37,7 @@ jobs: id: meta uses: docker/metadata-action@v5.5.1 with: - images: ghcr.io/datum-cloud/datum + images: ghcr.io/datum-cloud/${{ matrix.service }} tags: | type=schedule type=ref,event=branch @@ -40,11 +47,19 @@ jobs: type=semver,pattern={{major}} type=sha - - name: Build and push Docker image + - name: Build ${{ matrix.service }} id: push uses: docker/build-push-action@v6.7.0 with: context: . + file: cmd/${{ matrix.service }}/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-name: ghcr.io/datum-cloud/${{ matrix.service }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true diff --git a/Dockerfile b/cmd/datum-authorization-webhook/Dockerfile similarity index 74% rename from Dockerfile rename to cmd/datum-authorization-webhook/Dockerfile index e592d1d..d0227cb 100644 --- a/Dockerfile +++ b/cmd/datum-authorization-webhook/Dockerfile @@ -12,10 +12,10 @@ RUN go mod download COPY . . # Build the application -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o datum . +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o datum-authorization-webhook ./cmd/datum-authorization-webhook # Use a minimal image for the final container FROM gcr.io/distroless/static WORKDIR /app -COPY --from=builder /app/datum . -ENTRYPOINT ["/app/datum"] +COPY --from=builder /app/datum-authorization-webhook . +ENTRYPOINT ["/app/datum-authorization-webhook"] diff --git a/internal/authorization-webhook/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go similarity index 100% rename from internal/authorization-webhook/internal/iam/core_control_plane_authorizer.go rename to cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go diff --git a/internal/authorization-webhook/internal/iam/project_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go similarity index 100% rename from internal/authorization-webhook/internal/iam/project_control_plane_authorizer.go rename to cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go diff --git a/internal/authorization-webhook/internal/webhook/http.go b/cmd/datum-authorization-webhook/app/internal/webhook/http.go similarity index 100% rename from internal/authorization-webhook/internal/webhook/http.go rename to cmd/datum-authorization-webhook/app/internal/webhook/http.go diff --git a/internal/authorization-webhook/internal/webhook/response.go b/cmd/datum-authorization-webhook/app/internal/webhook/response.go similarity index 100% rename from internal/authorization-webhook/internal/webhook/response.go rename to cmd/datum-authorization-webhook/app/internal/webhook/response.go diff --git a/internal/authorization-webhook/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go similarity index 100% rename from internal/authorization-webhook/internal/webhook/webhook.go rename to cmd/datum-authorization-webhook/app/internal/webhook/webhook.go diff --git a/internal/authorization-webhook/cmd/serve.go b/cmd/datum-authorization-webhook/app/serve.go similarity index 96% rename from internal/authorization-webhook/cmd/serve.go rename to cmd/datum-authorization-webhook/app/serve.go index 3e950ed..66a2b60 100644 --- a/internal/authorization-webhook/cmd/serve.go +++ b/cmd/datum-authorization-webhook/app/serve.go @@ -1,12 +1,12 @@ -package cmd +package app import ( "context" "fmt" "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" - "go.datumapis.com/datum/internal/authorization-webhook/internal/iam" - authwebhook "go.datumapis.com/datum/internal/authorization-webhook/internal/webhook" + "go.datumapis.com/datum/cmd/datum-authorization-webhook/app/internal/iam" + authwebhook "go.datumapis.com/datum/cmd/datum-authorization-webhook/app/internal/webhook" "github.com/spf13/cobra" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" diff --git a/internal/authorization-webhook/cmd/webhook.go b/cmd/datum-authorization-webhook/app/webhook.go similarity index 82% rename from internal/authorization-webhook/cmd/webhook.go rename to cmd/datum-authorization-webhook/app/webhook.go index 4031c6a..cbadacd 100644 --- a/internal/authorization-webhook/cmd/webhook.go +++ b/cmd/datum-authorization-webhook/app/webhook.go @@ -1,8 +1,8 @@ -package cmd +package app import "github.com/spf13/cobra" -func Webhook() *cobra.Command { +func NewWebhook() *cobra.Command { cmd := &cobra.Command{ Use: "authorization-webhook", Short: "An authorization webhook backed by the Datum IAM service", diff --git a/cmd/datum-authorization-webhook/webhook.go b/cmd/datum-authorization-webhook/webhook.go new file mode 100644 index 0000000..4f068b0 --- /dev/null +++ b/cmd/datum-authorization-webhook/webhook.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "os" + + "go.datumapis.com/datum/cmd/datum-authorization-webhook/app" +) + +func main() { + if err := app.NewWebhook().Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go deleted file mode 100644 index bdf53ce..0000000 --- a/internal/cmd/root.go +++ /dev/null @@ -1,19 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" - "go.datumapis.com/datum/internal/authorization-webhook/cmd" -) - -var webhook = &cobra.Command{ - Use: "datum", - Short: "Datum Cloud", -} - -func init() { - webhook.AddCommand(cmd.Webhook()) -} - -func Execute() error { - return webhook.Execute() -} diff --git a/main.go b/main.go deleted file mode 100644 index e7ac067..0000000 --- a/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "go.datumapis.com/datum/internal/cmd" -) - -func main() { - if err := cmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} From 7cee717c80f41c35ef86eef020d075d3d474b2cd Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 21:20:11 +0000 Subject: [PATCH 04/27] chore: only run build on push to master --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 648dbf9..294ebd1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,8 +2,8 @@ name: Build and Publish Docker Image on: push: - # branches: - # - main + branches: + - main pull_request: jobs: From 77048e7c2e640f7ebadef069469c6b13882a989a Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 21:26:17 +0000 Subject: [PATCH 05/27] chore: rename tracing spans --- .../app/internal/iam/project_control_plane_authorizer.go | 2 +- cmd/datum-authorization-webhook/app/internal/webhook/http.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go index 27bc09b..3d5f12b 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go @@ -26,7 +26,7 @@ func (o *ProjectControlPlaneAuthorizer) Authorize( ctx context.Context, attributes authorizer.Attributes, ) (authorizer.Decision, string, error) { - ctx, span := otel.Tracer("go.datum.net/k8s-authz-webhook").Start(ctx, "datum.k8s-authz-webhook.Authorize", trace.WithAttributes( + ctx, span := otel.Tracer("go.datum.net/datum/cmd/datum-authorization-webhook").Start(ctx, "datum.authz-webhook.Authorize", trace.WithAttributes( attribute.String("subject", attributes.GetUser().GetName()), )) defer span.End() diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/http.go b/cmd/datum-authorization-webhook/app/internal/webhook/http.go index 1e40a51..f932e05 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/http.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/http.go @@ -68,7 +68,7 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { var body []byte var err error - ctx, span := otel.Tracer("go.datum.net/k8s-authz-webhook").Start(r.Context(), "datum.k8s-authz-webhook.HandleRequest") + ctx, span := otel.Tracer("go.datum.net/datum/cmd/datum-authorization-webhook").Start(r.Context(), "datum.authz-webhook.HandleRequest") defer span.End() if wh.WithContextFunc != nil { From 66bfec3bca7ea7707281d8c89204f46ab44826ae Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 21:29:18 +0000 Subject: [PATCH 06/27] feat: rely on env vars to configure tracing --- cmd/datum-authorization-webhook/app/serve.go | 47 ++++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/serve.go b/cmd/datum-authorization-webhook/app/serve.go index 66a2b60..56895c2 100644 --- a/cmd/datum-authorization-webhook/app/serve.go +++ b/cmd/datum-authorization-webhook/app/serve.go @@ -33,8 +33,8 @@ import ( ) func serveCommand() *cobra.Command { - var iamEndpoint, tracingEndpoint, certDir, certFile, keyFile string - var iamInsecure, tracingInsecure bool + var iamEndpoint, certDir, certFile, keyFile string + var iamInsecure bool var serveCmd = &cobra.Command{ Use: "serve", @@ -44,33 +44,24 @@ func serveCommand() *cobra.Command { return fmt.Errorf("`--iam-endpoint` is required") } - if tracingEndpoint != "" { - opts := []otlptracegrpc.Option{ - otlptracegrpc.WithEndpoint(tracingEndpoint), - } - if tracingInsecure { - opts = append(opts, otlptracegrpc.WithInsecure()) - } - - exporter, err := otlptrace.New(cmd.Context(), otlptracegrpc.NewClient(opts...)) - if err != nil { - return err - } - - otel.SetTracerProvider(trace.NewTracerProvider( - trace.WithSampler(trace.AlwaysSample()), - trace.WithResource(resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceName("authorization-webhook.datumapis.com"), - )), - trace.WithBatcher(exporter), - )) - otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( - propagation.TraceContext{}, - propagation.Baggage{}, - )) + exporter, err := otlptrace.New(cmd.Context(), otlptracegrpc.NewClient()) + if err != nil { + return err } + otel.SetTracerProvider(trace.NewTracerProvider( + trace.WithSampler(trace.AlwaysSample()), + trace.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName("authorization-webhook.datumapis.com"), + )), + trace.WithBatcher(exporter), + )) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + )) + dialOptions := []grpc.DialOption{ grpc.WithStatsHandler(otelgrpc.NewClientHandler()), grpc.WithChainUnaryInterceptor( @@ -147,8 +138,6 @@ func serveCommand() *cobra.Command { serveCmd.Flags().StringVar(&iamEndpoint, "iam-endpoint", "", "Endpoint to use for connecting to the datum gRPC API endpoint") serveCmd.Flags().BoolVar(&iamInsecure, "iam-endpoint-insecure", false, "Whether the use an insecure connection when export") - serveCmd.Flags().StringVar(&tracingEndpoint, "tracing-endpoint", "", "Endpoint to send OpenTelemetry traces too. Tracing is disabled if not provided.") - serveCmd.Flags().BoolVar(&tracingInsecure, "tracing-insecure", false, "Whether the use an insecure connection when exporting traces") serveCmd.Flags().StringVar(&certDir, "cert-dir", "", "Directory that contains the TLS certs to use for serving the webhook") serveCmd.Flags().StringVar(&certFile, "cert-file", "", "Filename in the directory that contains the TLS cert") serveCmd.Flags().StringVar(&keyFile, "key-file", "", "Filename in the directory that contains the TLS private key") From e18be36581fe4b17e0ce0360b4c6827b62a9f082 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 21:34:50 +0000 Subject: [PATCH 07/27] chore: align binary name with service name --- cmd/datum-authorization-webhook/app/webhook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/datum-authorization-webhook/app/webhook.go b/cmd/datum-authorization-webhook/app/webhook.go index cbadacd..87e84cc 100644 --- a/cmd/datum-authorization-webhook/app/webhook.go +++ b/cmd/datum-authorization-webhook/app/webhook.go @@ -4,7 +4,7 @@ import "github.com/spf13/cobra" func NewWebhook() *cobra.Command { cmd := &cobra.Command{ - Use: "authorization-webhook", + Use: "datum-authorization-webhook", Short: "An authorization webhook backed by the Datum IAM service", } From 077aa84ebab838eeb5d32c0e7083940c610b0a23 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 22:58:59 +0000 Subject: [PATCH 08/27] feat: require label selector for projects --- .../iam/core_control_plane_authorizer.go | 77 +++++++++++++++++-- go.mod | 15 ++-- go.sum | 44 +++++------ 3 files changed, 98 insertions(+), 38 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go index db172f7..2d08948 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go @@ -11,6 +11,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apiserver/pkg/authorization/authorizer" ) @@ -20,17 +21,13 @@ type CoreControlPlaneAuthorizer struct { IAMClient iamv1alphagrpc.AccessCheckClient } +const organizationUIDLabelKey = "resourcemanager.datumapis.com/organization-uid" + // Authorize implements authorizer.Authorizer. func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) { - req := &iampb.CheckAccessRequest{ - Resource: fmt.Sprintf("resourcemanager.datumapis.com/%s/%s", attributes.GetResource(), attributes.GetName()), - Subject: "user:" + attributes.GetUser().GetName(), - Permission: attributes.GetVerb(), - } ctx, span := otel.Tracer("go.datum.net/k8s-authz-webhook").Start(ctx, "datum.k8s-authz-webhook.global.Authorize", trace.WithAttributes( - attribute.String("subject", req.Subject), - attribute.String("resource", req.Resource), - attribute.String("permission", req.Permission), + attribute.String("api_group", attributes.GetAPIGroup()), + attribute.String("resource_kind", attributes.GetResource()), )) defer span.End() @@ -39,6 +36,25 @@ func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes a return authorizer.DecisionNoOpinion, "", nil } + labelSelector, err := attributes.GetLabelSelector() + if err != nil { + span.SetStatus(codes.Error, err.Error()) + return authorizer.DecisionNoOpinion, "", fmt.Errorf("failed to get label selector: %w", err) + } + + organizationID, err := getOrganizationID(labelSelector) + if err != nil { + return authorizer.DecisionNoOpinion, "", err + } + + req := getCheckAccessRequest(attributes, organizationID) + + span.SetAttributes( + attribute.String("subject", req.Subject), + attribute.String("resource", req.Resource), + attribute.String("permission", req.Permission), + ) + resp, err := o.IAMClient.CheckAccess(ctx, req) if err != nil { span.SetStatus(codes.Error, err.Error()) @@ -54,3 +70,48 @@ func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes a return authorizer.DecisionDeny, "", nil } + +func getCheckAccessRequest(attributes authorizer.Attributes, organizationID string) *iampb.CheckAccessRequest { + req := &iampb.CheckAccessRequest{ + Subject: "user:" + attributes.GetUser().GetName(), + Permission: fmt.Sprintf("%s/%s.%s", attributes.GetAPIGroup(), attributes.GetResource(), attributes.GetVerb()), + } + + // We want to perform the check against the organization resource when listing + // the projects from the server. + if attributes.GetVerb() == "list" { + req.Resource = "resourcemanager.datumapis.com/organizations/" + organizationID + } else { + req.Resource = fmt.Sprintf("resourcemanager.datumapis.com/%s/%s", attributes.GetResource(), attributes.GetName()) + req.Context = []*iampb.CheckContext{{ + ContextType: &iampb.CheckContext_ParentRelationship{ + ParentRelationship: &iampb.ParentRelationship{ + ParentResource: "resourcemanager.datumapis.com/organizations/" + organizationID, + ChildResource: req.Resource, + }, + }, + }} + } + + return req +} + +func getOrganizationID(selector labels.Requirements) (string, error) { + if len(selector) == 0 { + return "", fmt.Errorf("a label selector with `%s` is required to liist projects", organizationUIDLabelKey) + } + + var orgId string + for _, requirement := range selector { + if requirement.Key() != organizationUIDLabelKey { + continue + } + if len(requirement.Values()) != 1 { + return "", fmt.Errorf("the label selector for `%s` must have only one organization ID", organizationUIDLabelKey) + } + orgId = requirement.Values().List()[0] + break + } + + return orgId, nil +} diff --git a/go.mod b/go.mod index 231db1a..9667640 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 k8s.io/api v0.31.1 - k8s.io/apimachinery v0.31.1 + k8s.io/apimachinery v0.32.0 k8s.io/apiserver v0.31.1 sigs.k8s.io/controller-runtime v0.19.3 ) @@ -32,9 +32,9 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -73,13 +73,12 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/client-go v0.31.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 6fb8481..3de190a 100644 --- a/go.sum +++ b/go.sum @@ -31,13 +31,14 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -54,8 +55,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= @@ -86,10 +87,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -180,8 +181,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -203,7 +204,6 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -213,23 +213,23 @@ k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= -k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= -k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= +k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM= k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From 54fc44de90d737a6f7833695869ce0a662732724 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 23:16:50 +0000 Subject: [PATCH 09/27] debug: add response logging --- .../app/internal/webhook/http.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/http.go b/cmd/datum-authorization-webhook/app/internal/webhook/http.go index f932e05..f62e92a 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/http.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/http.go @@ -108,14 +108,19 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - slog.InfoContext(ctx, "received SubjectAccessReview webhook request", slog.Any("subject_access_review", req)) - if projectName := strings.TrimPrefix(r.RequestURI, "/v1alpha/"); strings.HasPrefix(projectName, "projects/") { span.SetAttributes(attribute.String("project", projectName)) ctx = context.WithValue(ctx, "resourcemanager.datumapis.com/project-name", projectName) } reviewResponse = wh.Handle(ctx, req) + + slog.InfoContext( + ctx, + "handled SubjectAccessReview webhook request", + slog.Any("request", req), + slog.Any("response", reviewResponse), + ) wh.writeResponse(w, &req, reviewResponse) } From 5fcdd6b8cbe1a137964fa3319886a03d071a0848 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 23:26:35 +0000 Subject: [PATCH 10/27] feat: add label selector to request --- .../app/internal/iam/core_control_plane_authorizer.go | 2 +- .../app/internal/webhook/webhook.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go index 2d08948..a88777f 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go @@ -98,7 +98,7 @@ func getCheckAccessRequest(attributes authorizer.Attributes, organizationID stri func getOrganizationID(selector labels.Requirements) (string, error) { if len(selector) == 0 { - return "", fmt.Errorf("a label selector with `%s` is required to liist projects", organizationUIDLabelKey) + return "", fmt.Errorf("a label selector with `%s` is required to list projects", organizationUIDLabelKey) } var orgId string diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index 0e55388..4e65390 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -5,6 +5,7 @@ import ( "net/http" authorizationv1 "k8s.io/api/authorization/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" ) @@ -33,6 +34,11 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { attrs.Subresource = resourceAttrs.Subresource attrs.Name = resourceAttrs.Name attrs.ResourceRequest = true + selector, err := labels.ParseToRequirements(r.Spec.ResourceAttributes.LabelSelector.RawSelector) + if err != nil { + attrs.LabelSelectorParsingErr = err + } + attrs.LabelSelectorRequirements = selector } if nonResourceAttrs := r.Spec.NonResourceAttributes; nonResourceAttrs != nil { From edd96f3535ab95f55056580b3c6f976139d7bf0c Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 23:35:16 +0000 Subject: [PATCH 11/27] bugfix: convert parsed label selectors --- .../app/internal/webhook/webhook.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index 4e65390..d653f7e 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -6,6 +6,7 @@ import ( authorizationv1 "k8s.io/api/authorization/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" ) @@ -34,11 +35,14 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { attrs.Subresource = resourceAttrs.Subresource attrs.Name = resourceAttrs.Name attrs.ResourceRequest = true - selector, err := labels.ParseToRequirements(r.Spec.ResourceAttributes.LabelSelector.RawSelector) - if err != nil { - attrs.LabelSelectorParsingErr = err + for _, requirement := range r.Spec.ResourceAttributes.LabelSelector.Requirements { + req, _ := labels.NewRequirement( + requirement.Key, + selection.Operator(requirement.Operator), + requirement.Values, + ) + attrs.LabelSelectorRequirements = append(attrs.LabelSelectorRequirements, *req) } - attrs.LabelSelectorRequirements = selector } if nonResourceAttrs := r.Spec.NonResourceAttributes; nonResourceAttrs != nil { From e5d868c5ef7cbdb501f80ee472dbc87f9ebb738c Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 13 Dec 2024 23:57:31 +0000 Subject: [PATCH 12/27] bugfix: add nil check on label selectors --- .../app/internal/webhook/webhook.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index d653f7e..b0ab7d4 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -35,13 +35,15 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { attrs.Subresource = resourceAttrs.Subresource attrs.Name = resourceAttrs.Name attrs.ResourceRequest = true - for _, requirement := range r.Spec.ResourceAttributes.LabelSelector.Requirements { - req, _ := labels.NewRequirement( - requirement.Key, - selection.Operator(requirement.Operator), - requirement.Values, - ) - attrs.LabelSelectorRequirements = append(attrs.LabelSelectorRequirements, *req) + if resourceAttrs.LabelSelector != nil { + for _, requirement := range resourceAttrs.LabelSelector.Requirements { + req, _ := labels.NewRequirement( + requirement.Key, + selection.Operator(requirement.Operator), + requirement.Values, + ) + attrs.LabelSelectorRequirements = append(attrs.LabelSelectorRequirements, *req) + } } } From c80811f56da98e2100e316fd97949c9a9ecb7a45 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sat, 14 Dec 2024 02:31:11 +0000 Subject: [PATCH 13/27] feat: use organization context --- .../iam/core_control_plane_authorizer.go | 9 ++--- .../app/internal/webhook/webhook.go | 35 ++++++++++++------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go index a88777f..7b9cb16 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go @@ -7,6 +7,7 @@ import ( "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" iampb "buf.build/gen/go/datum-cloud/iam/protocolbuffers/go/datum/iam/v1alpha" + "go.datumapis.com/datum/cmd/datum-authorization-webhook/app/internal/webhook" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" @@ -36,13 +37,7 @@ func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes a return authorizer.DecisionNoOpinion, "", nil } - labelSelector, err := attributes.GetLabelSelector() - if err != nil { - span.SetStatus(codes.Error, err.Error()) - return authorizer.DecisionNoOpinion, "", fmt.Errorf("failed to get label selector: %w", err) - } - - organizationID, err := getOrganizationID(labelSelector) + organizationID, err := webhook.GetOrganizationUID(ctx) if err != nil { return authorizer.DecisionNoOpinion, "", err } diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index b0ab7d4..43d02b6 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -2,15 +2,32 @@ package webhook import ( "context" + "fmt" "net/http" authorizationv1 "k8s.io/api/authorization/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" ) +type ContextKey string + +const OrganizationUIDContextKey ContextKey = "resourcemanager.datumapis.com/organization-uid" + +func GetOrganizationUID(ctx context.Context) (string, error) { + value := ctx.Value(OrganizationUIDContextKey) + if value == nil { + return "", fmt.Errorf("organization UID not set in context") + } + + orgID, ok := value.(string) + if !ok { + return "", fmt.Errorf("invalid organization ID set in context") + } + + return orgID, nil +} + func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { return &Webhook{ Handler: HandlerFunc(func(ctx context.Context, r Request) Response { @@ -18,6 +35,10 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { return Denied("must specify oneof resource or non-resource attributes, not both") } + if orgID := r.Spec.Extra["datum-organization-uid"]; len(orgID) > 0 { + ctx = context.WithValue(ctx, OrganizationUIDContextKey, orgID[0]) + } + attrs := authorizer.AttributesRecord{ User: &user.DefaultInfo{ Name: r.Spec.User, @@ -35,16 +56,6 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { attrs.Subresource = resourceAttrs.Subresource attrs.Name = resourceAttrs.Name attrs.ResourceRequest = true - if resourceAttrs.LabelSelector != nil { - for _, requirement := range resourceAttrs.LabelSelector.Requirements { - req, _ := labels.NewRequirement( - requirement.Key, - selection.Operator(requirement.Operator), - requirement.Values, - ) - attrs.LabelSelectorRequirements = append(attrs.LabelSelectorRequirements, *req) - } - } } if nonResourceAttrs := r.Spec.NonResourceAttributes; nonResourceAttrs != nil { From 057b36ccb7cd4c94eef02c1a9e8034293de84d39 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sat, 14 Dec 2024 02:37:15 +0000 Subject: [PATCH 14/27] bugfix: use organization resource for project creation --- .../app/internal/iam/core_control_plane_authorizer.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go index 7b9cb16..34c9dab 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "slices" "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" iampb "buf.build/gen/go/datum-cloud/iam/protocolbuffers/go/datum/iam/v1alpha" @@ -74,7 +75,7 @@ func getCheckAccessRequest(attributes authorizer.Attributes, organizationID stri // We want to perform the check against the organization resource when listing // the projects from the server. - if attributes.GetVerb() == "list" { + if slices.Contains([]string{"list", "create"}, attributes.GetVerb()) { req.Resource = "resourcemanager.datumapis.com/organizations/" + organizationID } else { req.Resource = fmt.Sprintf("resourcemanager.datumapis.com/%s/%s", attributes.GetResource(), attributes.GetName()) From 5aeee4a2bcc276b2b47d92be92d36d001a7f0a33 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sat, 14 Dec 2024 03:46:32 +0000 Subject: [PATCH 15/27] chore: remove unused code --- .../iam/core_control_plane_authorizer.go | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go index 34c9dab..150533b 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go @@ -13,7 +13,6 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apiserver/pkg/authorization/authorizer" ) @@ -23,8 +22,6 @@ type CoreControlPlaneAuthorizer struct { IAMClient iamv1alphagrpc.AccessCheckClient } -const organizationUIDLabelKey = "resourcemanager.datumapis.com/organization-uid" - // Authorize implements authorizer.Authorizer. func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) { ctx, span := otel.Tracer("go.datum.net/k8s-authz-webhook").Start(ctx, "datum.k8s-authz-webhook.global.Authorize", trace.WithAttributes( @@ -91,23 +88,3 @@ func getCheckAccessRequest(attributes authorizer.Attributes, organizationID stri return req } - -func getOrganizationID(selector labels.Requirements) (string, error) { - if len(selector) == 0 { - return "", fmt.Errorf("a label selector with `%s` is required to list projects", organizationUIDLabelKey) - } - - var orgId string - for _, requirement := range selector { - if requirement.Key() != organizationUIDLabelKey { - continue - } - if len(requirement.Values()) != 1 { - return "", fmt.Errorf("the label selector for `%s` must have only one organization ID", organizationUIDLabelKey) - } - orgId = requirement.Values().List()[0] - break - } - - return orgId, nil -} From 8f8ec5e008849805fe393a1845d4f0843f63471c Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sat, 14 Dec 2024 19:25:09 +0000 Subject: [PATCH 16/27] feat: use organization resource URL for resource watches --- .../app/internal/iam/core_control_plane_authorizer.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go index 150533b..57ea39a 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go @@ -70,9 +70,8 @@ func getCheckAccessRequest(attributes authorizer.Attributes, organizationID stri Permission: fmt.Sprintf("%s/%s.%s", attributes.GetAPIGroup(), attributes.GetResource(), attributes.GetVerb()), } - // We want to perform the check against the organization resource when listing - // the projects from the server. - if slices.Contains([]string{"list", "create"}, attributes.GetVerb()) { + // Use the organization resource URL when acting on resource collections. + if slices.Contains([]string{"list", "create", "watch"}, attributes.GetVerb()) { req.Resource = "resourcemanager.datumapis.com/organizations/" + organizationID } else { req.Resource = fmt.Sprintf("resourcemanager.datumapis.com/%s/%s", attributes.GetResource(), attributes.GetName()) From 39dbbb21c1ed8bfd4b6f4a6992554337c311cdad Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Wed, 18 Dec 2024 02:50:56 +0000 Subject: [PATCH 17/27] bugfix: use postfix path for webhook --- cmd/datum-authorization-webhook/app/serve.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/datum-authorization-webhook/app/serve.go b/cmd/datum-authorization-webhook/app/serve.go index 56895c2..a540e45 100644 --- a/cmd/datum-authorization-webhook/app/serve.go +++ b/cmd/datum-authorization-webhook/app/serve.go @@ -124,7 +124,7 @@ func serveCommand() *cobra.Command { entryLog.Info("registering webhooks to the webhook server") - hookServer.Register("/project/v1alpha/{project...}", authwebhook.NewAuthorizerWebhook(&iam.ProjectControlPlaneAuthorizer{ + hookServer.Register("/project/v1alpha/{project...}/webhook", authwebhook.NewAuthorizerWebhook(&iam.ProjectControlPlaneAuthorizer{ IAMClient: iamClient, })) hookServer.Register("/core/v1alpha/webhook", authwebhook.NewAuthorizerWebhook(&iam.CoreControlPlaneAuthorizer{ From 9d5c44220625b4c31462c4b8486f14d6cbc39358 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Wed, 18 Dec 2024 02:59:01 +0000 Subject: [PATCH 18/27] bugfix: use postfix path for webhook --- cmd/datum-authorization-webhook/app/serve.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/datum-authorization-webhook/app/serve.go b/cmd/datum-authorization-webhook/app/serve.go index a540e45..40482af 100644 --- a/cmd/datum-authorization-webhook/app/serve.go +++ b/cmd/datum-authorization-webhook/app/serve.go @@ -124,7 +124,7 @@ func serveCommand() *cobra.Command { entryLog.Info("registering webhooks to the webhook server") - hookServer.Register("/project/v1alpha/{project...}/webhook", authwebhook.NewAuthorizerWebhook(&iam.ProjectControlPlaneAuthorizer{ + hookServer.Register("/project/v1alpha/projects/{project}/webhook", authwebhook.NewAuthorizerWebhook(&iam.ProjectControlPlaneAuthorizer{ IAMClient: iamClient, })) hookServer.Register("/core/v1alpha/webhook", authwebhook.NewAuthorizerWebhook(&iam.CoreControlPlaneAuthorizer{ From 39e479528280022eaf86c0f6b0cd615a17bfbc94 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Wed, 18 Dec 2024 03:44:51 +0000 Subject: [PATCH 19/27] bugfix: extract project ID --- .../app/internal/iam/project_control_plane_authorizer.go | 3 ++- .../app/internal/webhook/http.go | 6 +++--- .../app/internal/webhook/webhook.go | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go index 3d5f12b..b19cb82 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go @@ -7,6 +7,7 @@ import ( "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" iampb "buf.build/gen/go/datum-cloud/iam/protocolbuffers/go/datum/iam/v1alpha" + "go.datumapis.com/datum/cmd/datum-authorization-webhook/app/internal/webhook" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -31,7 +32,7 @@ func (o *ProjectControlPlaneAuthorizer) Authorize( )) defer span.End() - projectNameContext := ctx.Value("resourcemanager.datumapis.com/project-name") + projectNameContext := ctx.Value(webhook.ProjectContextKey) projectName, ok := projectNameContext.(string) if !ok { span.SetStatus(codes.Error, "no project ID present in webhook request") diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/http.go b/cmd/datum-authorization-webhook/app/internal/webhook/http.go index f62e92a..3681a1d 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/http.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/http.go @@ -8,7 +8,6 @@ import ( "io" "log/slog" "net/http" - "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -108,9 +107,10 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if projectName := strings.TrimPrefix(r.RequestURI, "/v1alpha/"); strings.HasPrefix(projectName, "projects/") { + if projectID := r.PathValue("project"); projectID != "" { + projectName := "projects/" + projectID span.SetAttributes(attribute.String("project", projectName)) - ctx = context.WithValue(ctx, "resourcemanager.datumapis.com/project-name", projectName) + ctx = context.WithValue(ctx, ProjectContextKey, projectName) } reviewResponse = wh.Handle(ctx, req) diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index 43d02b6..7b1efbd 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -13,6 +13,7 @@ import ( type ContextKey string const OrganizationUIDContextKey ContextKey = "resourcemanager.datumapis.com/organization-uid" +const ProjectContextKey ContextKey = "resourcemanager.datumapis.com/project-name" func GetOrganizationUID(ctx context.Context) (string, error) { value := ctx.Value(OrganizationUIDContextKey) From 1ff26395dc28a6ad79a1db47fb0a7acac192b919 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sun, 29 Dec 2024 21:32:51 +0000 Subject: [PATCH 20/27] feat: use fully qualified label format --- cmd/datum-authorization-webhook/app/internal/webhook/webhook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index 7b1efbd..826acce 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -36,7 +36,7 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { return Denied("must specify oneof resource or non-resource attributes, not both") } - if orgID := r.Spec.Extra["datum-organization-uid"]; len(orgID) > 0 { + if orgID := r.Spec.Extra["authentication.datum.net/datum-organization-uid"]; len(orgID) > 0 { ctx = context.WithValue(ctx, OrganizationUIDContextKey, orgID[0]) } From d1b3b973cb01472107e1968023bb5dece91fb6e9 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 17 Jan 2025 17:56:38 +0000 Subject: [PATCH 21/27] feat: align organization ID naming The organization ID that's used for the API control plane is now the ID that the user either provides, or is created for them automatically based on the name of the organization. The label has been updated to match. --- .../app/internal/webhook/webhook.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index 826acce..552f398 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -12,7 +12,7 @@ import ( type ContextKey string -const OrganizationUIDContextKey ContextKey = "resourcemanager.datumapis.com/organization-uid" +const OrganizationUIDContextKey ContextKey = "resourcemanager.datumapis.com/organization-id" const ProjectContextKey ContextKey = "resourcemanager.datumapis.com/project-name" func GetOrganizationUID(ctx context.Context) (string, error) { @@ -36,7 +36,7 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { return Denied("must specify oneof resource or non-resource attributes, not both") } - if orgID := r.Spec.Extra["authentication.datum.net/datum-organization-uid"]; len(orgID) > 0 { + if orgID := r.Spec.Extra[string(OrganizationUIDContextKey)]; len(orgID) > 0 { ctx = context.WithValue(ctx, OrganizationUIDContextKey, orgID[0]) } From d4de5dfdfa8cfc360d1b074debce6109bf6df28c Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 17 Jan 2025 18:53:42 +0000 Subject: [PATCH 22/27] chore: align function name with context key --- .../app/internal/iam/core_control_plane_authorizer.go | 2 +- .../app/internal/webhook/webhook.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go index 57ea39a..446534d 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go @@ -35,7 +35,7 @@ func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes a return authorizer.DecisionNoOpinion, "", nil } - organizationID, err := webhook.GetOrganizationUID(ctx) + organizationID, err := webhook.GetOrganizationID(ctx) if err != nil { return authorizer.DecisionNoOpinion, "", err } diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index 552f398..d2b43db 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -12,11 +12,11 @@ import ( type ContextKey string -const OrganizationUIDContextKey ContextKey = "resourcemanager.datumapis.com/organization-id" +const OrganizationIDContextKey ContextKey = "resourcemanager.datumapis.com/organization-id" const ProjectContextKey ContextKey = "resourcemanager.datumapis.com/project-name" -func GetOrganizationUID(ctx context.Context) (string, error) { - value := ctx.Value(OrganizationUIDContextKey) +func GetOrganizationID(ctx context.Context) (string, error) { + value := ctx.Value(OrganizationIDContextKey) if value == nil { return "", fmt.Errorf("organization UID not set in context") } @@ -36,8 +36,8 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { return Denied("must specify oneof resource or non-resource attributes, not both") } - if orgID := r.Spec.Extra[string(OrganizationUIDContextKey)]; len(orgID) > 0 { - ctx = context.WithValue(ctx, OrganizationUIDContextKey, orgID[0]) + if orgID := r.Spec.Extra[string(OrganizationIDContextKey)]; len(orgID) > 0 { + ctx = context.WithValue(ctx, OrganizationIDContextKey, orgID[0]) } attrs := authorizer.AttributesRecord{ From 63b20a34e8c8429c444f79f690b64abcae9839be Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sat, 18 Jan 2025 01:31:28 +0000 Subject: [PATCH 23/27] feat: pass context through extra attributes --- .../iam/core_control_plane_authorizer.go | 10 +++++--- .../iam/project_control_plane_authorizer.go | 13 ++++++---- .../app/internal/webhook/http.go | 2 +- .../app/internal/webhook/webhook.go | 25 +++++-------------- 4 files changed, 22 insertions(+), 28 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go index 446534d..1c402f8 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go @@ -35,9 +35,13 @@ func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes a return authorizer.DecisionNoOpinion, "", nil } - organizationID, err := webhook.GetOrganizationID(ctx) - if err != nil { - return authorizer.DecisionNoOpinion, "", err + var organizationID string + if orgIDs, set := attributes.GetUser().GetExtra()[webhook.OrganizationIDExtraKey]; !set { + return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' is required by core control plane authorizer", webhook.OrganizationIDExtraKey) + } else if len(orgIDs) > 1 { + return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' only supports one value, but multiple were provided: %v", orgIDs) + } else { + organizationID = orgIDs[0] } req := getCheckAccessRequest(attributes, organizationID) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go index b19cb82..4ab77b4 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go @@ -32,12 +32,15 @@ func (o *ProjectControlPlaneAuthorizer) Authorize( )) defer span.End() - projectNameContext := ctx.Value(webhook.ProjectContextKey) - projectName, ok := projectNameContext.(string) - if !ok { + var projectName string + if projectNames, set := attributes.GetUser().GetExtra()[webhook.ProjectExtraKey]; !set { span.SetStatus(codes.Error, "no project ID present in webhook request") - slog.WarnContext(ctx, "no project name was present in the webhook authorize request") - return authorizer.DecisionNoOpinion, "", nil + return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' is required by core control plane authorizer", webhook.ProjectExtraKey) + } else if len(projectNames) > 1 { + span.SetStatus(codes.Error, "multiple project IDs present in webhook request") + return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' only supports one value, but multiple were provided: %v", webhook.ProjectExtraKey, projectNames) + } else { + projectName = projectNames[0] } resourceURL := "resourcemanager.datumapis.com/" + projectName diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/http.go b/cmd/datum-authorization-webhook/app/internal/webhook/http.go index 3681a1d..5e40565 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/http.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/http.go @@ -110,7 +110,7 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { if projectID := r.PathValue("project"); projectID != "" { projectName := "projects/" + projectID span.SetAttributes(attribute.String("project", projectName)) - ctx = context.WithValue(ctx, ProjectContextKey, projectName) + req.Spec.Extra[ProjectExtraKey] = []string{projectName} } reviewResponse = wh.Handle(ctx, req) diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index d2b43db..5d3134e 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -2,7 +2,6 @@ package webhook import ( "context" - "fmt" "net/http" authorizationv1 "k8s.io/api/authorization/v1" @@ -12,22 +11,8 @@ import ( type ContextKey string -const OrganizationIDContextKey ContextKey = "resourcemanager.datumapis.com/organization-id" -const ProjectContextKey ContextKey = "resourcemanager.datumapis.com/project-name" - -func GetOrganizationID(ctx context.Context) (string, error) { - value := ctx.Value(OrganizationIDContextKey) - if value == nil { - return "", fmt.Errorf("organization UID not set in context") - } - - orgID, ok := value.(string) - if !ok { - return "", fmt.Errorf("invalid organization ID set in context") - } - - return orgID, nil -} +const OrganizationIDExtraKey = "resourcemanager.datumapis.com/organization-id" +const ProjectExtraKey = "resourcemanager.datumapis.com/project-name" func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { return &Webhook{ @@ -36,8 +21,9 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { return Denied("must specify oneof resource or non-resource attributes, not both") } - if orgID := r.Spec.Extra[string(OrganizationIDContextKey)]; len(orgID) > 0 { - ctx = context.WithValue(ctx, OrganizationIDContextKey, orgID[0]) + extra := map[string][]string{} + for key, val := range r.Spec.Extra { + extra[key] = []string(val) } attrs := authorizer.AttributesRecord{ @@ -45,6 +31,7 @@ func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { Name: r.Spec.User, UID: r.Spec.UID, Groups: r.Spec.Groups, + Extra: extra, }, } From c03c2aaa062ed25c8a60a18914c8e918f99d3e26 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sat, 18 Jan 2025 01:32:22 +0000 Subject: [PATCH 24/27] chore: remove debug logging --- cmd/datum-authorization-webhook/app/serve.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/serve.go b/cmd/datum-authorization-webhook/app/serve.go index 40482af..39fe49c 100644 --- a/cmd/datum-authorization-webhook/app/serve.go +++ b/cmd/datum-authorization-webhook/app/serve.go @@ -20,8 +20,6 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" "k8s.io/api/authentication/v1beta1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client/config" @@ -64,15 +62,6 @@ func serveCommand() *cobra.Command { dialOptions := []grpc.DialOption{ grpc.WithStatsHandler(otelgrpc.NewClientHandler()), - grpc.WithChainUnaryInterceptor( - func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { - logger := log.Log.WithName("grpc_client") - logger.Info(method, ".request: ", protojson.Format(req.(proto.Message))) - err := invoker(ctx, method, req, reply, cc, opts...) - logger.Info(method, ".response: ", protojson.Format(reply.(proto.Message))) - return err - }, - ), } if iamInsecure { From 5757fdc442df439f39090662d6e4ac03cebd1e87 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sat, 18 Jan 2025 01:33:07 +0000 Subject: [PATCH 25/27] chore: remove unused webhook code --- .../app/internal/webhook/response.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/response.go b/cmd/datum-authorization-webhook/app/internal/webhook/response.go index 3079eea..37bfc9d 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/response.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/response.go @@ -5,18 +5,6 @@ import ( authorizationv1 "k8s.io/api/authorization/v1" ) -// Allowed constructs a response indicating that the given user is authorized -// to perform the given action. The reason parameter is optional. -func Allowed(reason string) Response { - return AuthorizationResponse(true, false, reason, "") -} - -// NoOpinion constructs a response indicating that the authorizer has no rule -// allowing the request, but also no rule specifically denying the request -func NoOpinion() Response { - return AuthorizationResponse(false, false, "", "") -} - // Denied constructs a response indicating that the given user is denied // to perform the given action. The reason parameter is optional. func Denied(reason string) Response { From 0d85fdd4af7139358c7ad780e8989386df44cab2 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sat, 18 Jan 2025 01:44:40 +0000 Subject: [PATCH 26/27] chore: fix error message --- .../app/internal/iam/core_control_plane_authorizer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go index 1c402f8..76ebe8d 100644 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go @@ -39,7 +39,7 @@ func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes a if orgIDs, set := attributes.GetUser().GetExtra()[webhook.OrganizationIDExtraKey]; !set { return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' is required by core control plane authorizer", webhook.OrganizationIDExtraKey) } else if len(orgIDs) > 1 { - return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' only supports one value, but multiple were provided: %v", orgIDs) + return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' only supports one value, but multiple were provided: %v", webhook.OrganizationIDExtraKey, orgIDs) } else { organizationID = orgIDs[0] } From 6a0da6ee6fb6d8bd2b180f1bcd755dd0d7e5d4ce Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Sat, 18 Jan 2025 02:01:01 +0000 Subject: [PATCH 27/27] chore: remove unnecessary code --- cmd/datum-authorization-webhook/app/internal/webhook/http.go | 2 -- cmd/datum-authorization-webhook/app/internal/webhook/webhook.go | 2 -- 2 files changed, 4 deletions(-) diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/http.go b/cmd/datum-authorization-webhook/app/internal/webhook/http.go index 5e40565..d9be2ff 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/http.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/http.go @@ -118,8 +118,6 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { slog.InfoContext( ctx, "handled SubjectAccessReview webhook request", - slog.Any("request", req), - slog.Any("response", reviewResponse), ) wh.writeResponse(w, &req, reviewResponse) } diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go index 5d3134e..ebcaff9 100644 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go @@ -9,8 +9,6 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" ) -type ContextKey string - const OrganizationIDExtraKey = "resourcemanager.datumapis.com/organization-id" const ProjectExtraKey = "resourcemanager.datumapis.com/project-name"