diff --git a/.github/actions/setup-caches/action.yaml b/.github/actions/setup-caches/action.yaml index 56c05e50..6620ebe6 100644 --- a/.github/actions/setup-caches/action.yaml +++ b/.github/actions/setup-caches/action.yaml @@ -9,11 +9,11 @@ inputs: runs: using: composite steps: - - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-pkg-mod-${{ hashFiles('**/go.sum') }}-${{ hashFiles('Makefile') }} - - uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 if: ${{ inputs.build-cache-key }} with: path: ~/.cache/go-build diff --git a/.github/workflows/check-actions.yml b/.github/workflows/check-actions.yml index 67b6da05..d877b020 100644 --- a/.github/workflows/check-actions.yml +++ b/.github/workflows/check-actions.yml @@ -17,7 +17,7 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Ensure SHA pinned actions - uses: zgosalvez/github-actions-ensure-sha-pinned-actions@70c4af2ed5282c51ba40566d026d6647852ffa3e # v5.0.1 + uses: zgosalvez/github-actions-ensure-sha-pinned-actions@ca46236c6ce584ae24bc6283ba8dcf4b3ec8a066 # v5.0.4 with: # slsa-github-generator requires using a semver tag for reusable workflows. # See: https://github.com/slsa-framework/slsa-github-generator#referencing-slsa-builders-and-generators diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8020b6ba..e1311d41 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -27,12 +27,12 @@ jobs: value: ${{ secrets.FOSSA_API_KEY }} - name: "Run FOSSA Scan" if: steps.checksecret.outputs.result == 'true' - uses: fossas/fossa-action@c414b9ad82eaad041e47a7cf62a4f02411f427a0 # v1.8.0 + uses: fossas/fossa-action@ff70fe9fe17cbd2040648f1c45e8ec4e4884dcf3 # v1.9.0 with: api-key: ${{ secrets.FOSSA_API_KEY }} - name: "Run FOSSA Test" if: steps.checksecret.outputs.result == 'true' - uses: fossas/fossa-action@c414b9ad82eaad041e47a7cf62a4f02411f427a0 # v1.8.0 + uses: fossas/fossa-action@ff70fe9fe17cbd2040648f1c45e8ec4e4884dcf3 # v1.9.0 with: api-key: ${{ secrets.FOSSA_API_KEY }} run-tests: true @@ -48,15 +48,15 @@ jobs: steps: - name: Checkout Source uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: 'go.mod' - name: Run Gosec Security Scanner - uses: securego/gosec@bb17e422fc34bf4c0a2e5cab9d07dc45a68c040c # v2.24.7 + uses: securego/gosec@223e19b8856e00f02cc67804499a83f77e208f3c # v2.25.0 with: args: '-no-fail -fmt sarif -out gosec.sarif ./...' - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@1a97b0f94ec9297d6f58aefe5a6b5441c045bed4 + uses: github/codeql-action/upload-sarif@34950e1b113b30df4edee1a6d3a605242df0c40b with: sarif_file: gosec.sarif unit_tests: @@ -65,7 +65,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: 'go.mod' - name: Unit Test @@ -77,7 +77,7 @@ jobs: value: ${{ secrets.CODECOV_TOKEN }} - name: Upload Report to Codecov if: ${{ steps.checksecret.outputs.result == 'true' }} - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5.5.4 with: token: ${{ secrets.CODECOV_TOKEN }} slug: projectcapsule/capsule-proxy diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 5194d142..7e952ec5 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -40,6 +40,6 @@ jobs: # See: https://github.com/aquasecurity/trivy-action/issues/389#issuecomment-2385416577 TRIVY_DB_REPOSITORY: 'public.ecr.aws/aquasecurity/trivy-db:2' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@1a97b0f94ec9297d6f58aefe5a6b5441c045bed4 + uses: github/codeql-action/upload-sarif@34950e1b113b30df4edee1a6d3a605242df0c40b with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f1f4b97b..4ad70745 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: 'go.mod' - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d36efa02..cda5408b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: 'go.mod' - name: Generate manifests @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: 'go.mod' - name: Run golangci-lint diff --git a/.github/workflows/releaser.yml b/.github/workflows/releaser.yml index 89cd721a..dea623d6 100644 --- a/.github/workflows/releaser.yml +++ b/.github/workflows/releaser.yml @@ -26,7 +26,7 @@ jobs: timeout-minutes: 5 continue-on-error: true - uses: creekorful/goreportcard-action@1f35ced8cdac2cba28c9a2f2288a16aacfd507f9 # v1.0 - - uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d + - uses: anchore/sbom-action/download-syft@f0d33c151c04af6fcbf4363834e838fcc7c87783 - name: Install Cosign uses: sigstore/cosign-installer@fb28c2b6339dcd94da6e4cbcbc5e888961f6f8c3 # DO NOT UPDATE v3.9.0 - name: Run GoReleaser diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index bfff9c1e..9c1062fe 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -37,6 +37,6 @@ jobs: path: results.sarif retention-days: 5 - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: sarif_file: results.sarif diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b30ea587..15e5924d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,7 +15,7 @@ jobs: pull-requests: write steps: - name: Close stale pull requests - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f + uses: actions/stale@db5d06a4c82d5e94513c09c406638111df61f63e with: stale-issue-message: 'This pull request has been automatically closed because it has been inactive for more than 60 days. Please reopen if you still intend to submit this pull request.' days-before-stale: 60 diff --git a/Makefile b/Makefile index 14a8a7ba..82237541 100644 --- a/Makefile +++ b/Makefile @@ -190,7 +190,7 @@ ifeq ($(CAPSULE_PROXY_MODE),http) --set "serviceMonitor.enabled=false" \ --set "options.generateCertificates=false" \ --set "certManager.generateCertificates=false" \ - --set "options.extraArgs={--feature-gates=ProxyClusterScoped=true,--feature-gates=ProxyAllNamespaced=true}" + --set "options.extraArgs={--feature-gates=ProxyAllNamespaced=true}" else @echo "Running in HTTPS mode" @echo "Installing Capsule-Proxy using HELM..." @@ -206,7 +206,7 @@ else --set "serviceMonitor.enabled=false" \ --set "options.generateCertificates=false" \ --set "certManager.certificate.ipAddresses={127.0.0.1}" \ - --set "options.extraArgs={--feature-gates=ProxyClusterScoped=true,--feature-gates=ProxyAllNamespaced=true}" + --set "options.extraArgs={--feature-gates=ProxyAllNamespaced=true}" endif @kubectl rollout restart ds capsule-proxy -n capsule-system || true $(MAKE) generate-kubeconfigs diff --git a/api/v1beta1/proxysettings_types.go b/api/v1beta1/proxysettings_types.go index bf9ab52e..a9708b27 100644 --- a/api/v1beta1/proxysettings_types.go +++ b/api/v1beta1/proxysettings_types.go @@ -13,10 +13,12 @@ type OwnerSpec struct { Kind capsuleapi.OwnerKind `json:"kind"` // Name of tenant owner. Name string `json:"name"` - // Proxy settings for tenant owner. - ProxyOperations []capsuleapi.ProxySettings `json:"proxySettings,omitempty"` // Cluster Resources for tenant Owner. ClusterResources []ClusterResource `json:"clusterResources,omitempty"` + // Deprecated: Use Global Proxy Settings instead (https://projectcapsule.dev/docs/proxy/proxysettings/#globalproxysettings) + // + // Proxy settings for tenant owner. + ProxyOperations []capsuleapi.ProxySettings `json:"proxySettings,omitempty"` } // ProxySettingSpec defines the additional Capsule Proxy settings for additional users of the Tenant. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index a1cc31d9..857990ab 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -173,16 +173,16 @@ func (in *GlobalSubjectSpec) DeepCopy() *GlobalSubjectSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) { *out = *in - if in.ProxyOperations != nil { - in, out := &in.ProxyOperations, &out.ProxyOperations - *out = make([]api.ProxySettings, len(*in)) + if in.ClusterResources != nil { + in, out := &in.ClusterResources, &out.ClusterResources + *out = make([]ClusterResource, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.ClusterResources != nil { - in, out := &in.ClusterResources, &out.ClusterResources - *out = make([]ClusterResource, len(*in)) + if in.ProxyOperations != nil { + in, out := &in.ProxyOperations, &out.ProxyOperations + *out = make([]api.ProxySettings, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/charts/capsule-proxy/Chart.yaml b/charts/capsule-proxy/Chart.yaml index 7fef34aa..28173a1d 100644 --- a/charts/capsule-proxy/Chart.yaml +++ b/charts/capsule-proxy/Chart.yaml @@ -33,5 +33,5 @@ annotations: - name: Documentation url: https://projectcapsule.dev/docs/proxy/ artifacthub.io/changes: | - - kind: added - description: added toggles for podSecurityContexts and securityContexts + - kind: fixed + description: fixed default securityContext toggles for kubectl job diff --git a/charts/capsule-proxy/README.md b/charts/capsule-proxy/README.md index 65305177..a91b6af0 100644 --- a/charts/capsule-proxy/README.md +++ b/charts/capsule-proxy/README.md @@ -95,7 +95,7 @@ If you only need to make minor customizations, you can specify them on the comma | global.jobs.certs.image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy of the post install certgen job | | global.jobs.certs.image.registry | string | `"registry.k8s.io"` | Set the image repository of the post install certgen job | | global.jobs.certs.image.repository | string | `"ingress-nginx/kube-webhook-certgen"` | Set the image repository of the post install certgen job | -| global.jobs.certs.image.tag | string | `"v1.6.7"` | Set the image tag of the post install certgen job | +| global.jobs.certs.image.tag | string | `"v1.6.9"` | Set the image tag of the post install certgen job | | global.jobs.certs.nodeSelector | object | `{}` | Set the node selector | | global.jobs.certs.podSecurityContext | object | `{"enabled":true,"seccompProfile":{"type":"RuntimeDefault"}}` | Security context for the job pods. | | global.jobs.certs.priorityClassName | string | `""` | Set a pod priorityClassName | @@ -115,11 +115,11 @@ If you only need to make minor customizations, you can specify them on the comma | global.jobs.kubectl.nodeSelector | object | `{}` | Set the node selector | | global.jobs.kubectl.podAnnotations | object | `{}` | Annotations to add to the job pod | | global.jobs.kubectl.podLabels | object | `{}` | Labels to add to the job pod | -| global.jobs.kubectl.podSecurityContext | object | `{"enabled":false,"seccompProfile":{"type":"RuntimeDefault"}}` | Security context for the job pods. | +| global.jobs.kubectl.podSecurityContext | object | `{"enabled":true,"seccompProfile":{"type":"RuntimeDefault"}}` | Security context for the job pods. | | global.jobs.kubectl.priorityClassName | string | `""` | Set a pod priorityClassName | | global.jobs.kubectl.resources | object | `{}` | Job resources | | global.jobs.kubectl.restartPolicy | string | `"Never"` | Set the restartPolicy | -| global.jobs.kubectl.securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":false,"readOnlyRootFilesystem":true,"runAsGroup":1002,"runAsNonRoot":true,"runAsUser":1002}` | Security context for the job containers. | +| global.jobs.kubectl.securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":true,"readOnlyRootFilesystem":true,"runAsGroup":1002,"runAsNonRoot":true,"runAsUser":1002}` | Security context for the job containers. | | global.jobs.kubectl.tolerations | list | `[]` | Set list of tolerations | | global.jobs.kubectl.topologySpreadConstraints | list | `[]` | Set Topology Spread Constraints | | global.jobs.kubectl.ttlSecondsAfterFinished | int | `60` | Sets the ttl in seconds after a finished certgen job is deleted. Set to -1 to never delete. | @@ -136,6 +136,7 @@ If you only need to make minor customizations, you can specify them on the comma | daemonset.hostNetwork | bool | `false` | Use the host network namespace for capsule-proxy pod. | | daemonset.hostPort | bool | `false` | Binding the capsule-proxy listening port to the host port. | | env | list | `[]` | Additional environment variables | +| extraManifests | list | `[]` | Array of additional resources to be created alongside Capsule-Proxy helm chart | | hostNetwork | bool | `false` | When deployed as DaemonSet use | | hostUsers | bool | `true` | Don't use Host Users (User Namespaces) | | image.pullPolicy | string | `"IfNotPresent"` | Set the image pull policy. | diff --git a/charts/capsule-proxy/crds/capsule.clastix.io_proxysettings.yaml b/charts/capsule-proxy/crds/capsule.clastix.io_proxysettings.yaml index 6cc29dbe..7a2928da 100644 --- a/charts/capsule-proxy/crds/capsule.clastix.io_proxysettings.yaml +++ b/charts/capsule-proxy/crds/capsule.clastix.io_proxysettings.yaml @@ -142,7 +142,10 @@ spec: description: Name of tenant owner. type: string proxySettings: - description: Proxy settings for tenant owner. + description: |- + Deprecated: Use Global Proxy Settings instead (https://projectcapsule.dev/docs/proxy/proxysettings/#globalproxysettings) + + Proxy settings for tenant owner. items: properties: kind: diff --git a/charts/capsule-proxy/templates/extra-manifests.yaml b/charts/capsule-proxy/templates/extra-manifests.yaml new file mode 100644 index 00000000..567f7bf3 --- /dev/null +++ b/charts/capsule-proxy/templates/extra-manifests.yaml @@ -0,0 +1,4 @@ +{{ range .Values.extraManifests }} +--- +{{ tpl (toYaml .) $ }} +{{ end }} diff --git a/charts/capsule-proxy/values.schema.json b/charts/capsule-proxy/values.schema.json index 5284d076..be3d0076 100644 --- a/charts/capsule-proxy/values.schema.json +++ b/charts/capsule-proxy/values.schema.json @@ -154,6 +154,10 @@ "description": "Additional environment variables", "type": "array" }, + "extraManifests": { + "description": "Array of additional resources to be created alongside Capsule-Proxy helm chart", + "type": "array" + }, "gangplank": { "type": "object", "properties": { diff --git a/charts/capsule-proxy/values.yaml b/charts/capsule-proxy/values.yaml index f78de719..d7d49fff 100644 --- a/charts/capsule-proxy/values.yaml +++ b/charts/capsule-proxy/values.yaml @@ -31,12 +31,12 @@ global: ttlSecondsAfterFinished: 60 # -- Security context for the job pods. podSecurityContext: - enabled: false + enabled: true seccompProfile: type: "RuntimeDefault" # -- Security context for the job containers. securityContext: - enabled: false + enabled: true allowPrivilegeEscalation: false capabilities: drop: @@ -68,7 +68,7 @@ global: # -- Set the image pull policy of the post install certgen job pullPolicy: IfNotPresent # -- Set the image tag of the post install certgen job - tag: "v1.6.7" + tag: "v1.6.9" # -- Annotations to add to the certgen job. annotations: {} # -- Set the restartPolicy @@ -471,6 +471,15 @@ serviceMonitor: # -- Set relabelings for the endpoint of the serviceMonitor relabelings: [] +# -- Array of additional resources to be created alongside Capsule-Proxy helm chart +extraManifests: [] + # - apiVersion: v1 + # kind: ConfigMap + # metadata: + # name: extra-configmap + # data: + # key: value + # Deploys Gangplank for OIDC Kubeconfig Generation gangplank: # -- Enable Gangplank diff --git a/go.mod b/go.mod index c6b740d4..14eaebbd 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/spf13/pflag v1.0.10 github.com/thediveo/enumflag v0.10.1 go.uber.org/zap v1.27.1 - golang.org/x/net v0.51.0 + golang.org/x/net v0.52.0 k8s.io/api v0.34.2 k8s.io/apiextensions-apiserver v0.34.2 k8s.io/apimachinery v0.34.2 @@ -24,7 +24,7 @@ require ( k8s.io/client-go v0.34.2 k8s.io/component-base v0.34.2 k8s.io/kubectl v0.34.0 - k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 + k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 sigs.k8s.io/controller-runtime v0.22.4 ) @@ -77,14 +77,14 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.32.0 // indirect + golang.org/x/mod v0.33.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/term v0.40.0 // indirect - golang.org/x/text v0.34.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/term v0.41.0 // indirect + golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/tools v0.42.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect diff --git a/go.sum b/go.sum index 1a601139..31752bbd 100644 --- a/go.sum +++ b/go.sum @@ -287,6 +287,8 @@ 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/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -299,6 +301,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= @@ -310,6 +314,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -321,12 +327,18 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= 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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= @@ -339,6 +351,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= 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= @@ -392,6 +406,8 @@ k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs= k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= diff --git a/internal/authorization/middleware.go b/internal/authorization/middleware.go index 7921b36d..45e9dd8a 100644 --- a/internal/authorization/middleware.go +++ b/internal/authorization/middleware.go @@ -12,7 +12,7 @@ import ( "github.com/projectcapsule/capsule-proxy/internal/tenant" ) -func MutateAuthorization(proxyClusterScoped bool, proxyTenants []*tenant.ProxyTenant, obj *runtime.Object, gvk schema.GroupVersionKind) error { +func MutateAuthorization(proxyTenants []*tenant.ProxyTenant, obj *runtime.Object, gvk schema.GroupVersionKind) error { switch gvk.Kind { case "SelfSubjectAccessReview": //nolint:forcetypeassert @@ -21,10 +21,6 @@ func MutateAuthorization(proxyClusterScoped bool, proxyTenants []*tenant.ProxyTe accessReview.Status.Allowed = true } - if !proxyClusterScoped { - return nil - } - accessReviewGvk := schema.GroupVersionKind{ Group: accessReview.Spec.ResourceAttributes.Group, Version: accessReview.Spec.ResourceAttributes.Version, @@ -48,11 +44,8 @@ func MutateAuthorization(proxyClusterScoped bool, proxyTenants []*tenant.ProxyTe rules := (*obj).(*authorizationv1.SelfSubjectRulesReview) var resourceRules []authorizationv1.ResourceRule - if proxyClusterScoped { - resourceRules = getAllResourceRules(proxyTenants) - } else { - resourceRules = []authorizationv1.ResourceRule{} - } + + resourceRules = getAllResourceRules(proxyTenants) resourceRules = append(resourceRules, authorizationv1.ResourceRule{ APIGroups: []string{""}, diff --git a/internal/features/features.go b/internal/features/features.go index 8e05d508..4d2550b9 100644 --- a/internal/features/features.go +++ b/internal/features/features.go @@ -19,7 +19,8 @@ const ( // essentially bypassing any authorization. Only use this option in trusted environments // where authorization/authentication is offloaded to external systems. SkipImpersonationReview = "SkipImpersonationReview" - + // Deprecated: Use Global Proxy Settings instead (https://projectcapsule.dev/docs/proxy/proxysettings/#globalproxysettings) + // // ProxyClusterScoped allows to proxy all clusterScoped objects // for all tenant users. ProxyClusterScoped = "ProxyClusterScoped" diff --git a/internal/indexer/tenant_owner.go b/internal/indexer/tenant_owner.go new file mode 100644 index 00000000..1e180a61 --- /dev/null +++ b/internal/indexer/tenant_owner.go @@ -0,0 +1,42 @@ +// Copyright 2020-2026 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package indexer + +import ( + "fmt" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + TenantOwnerKindField = ".status.owner.ownerkind" +) + +// TenantOwnerReference indexes Tenants by their status.owners (Kind:Name). +type TenantOwnerReference struct{} + +func (o TenantOwnerReference) Object() client.Object { + return &capsulev1beta2.Tenant{} +} + +func (o TenantOwnerReference) Field() string { + return TenantOwnerKindField +} + +func (o TenantOwnerReference) Func() client.IndexerFunc { + return func(object client.Object) []string { + tnt, ok := object.(*capsulev1beta2.Tenant) + if !ok { + panic(fmt.Errorf("expected type *capsulev1beta2.Tenant, got %T", object)) + } + + var owners []string + for _, owner := range tnt.Status.Owners { + owners = append(owners, fmt.Sprintf("%s:%s", owner.Kind.String(), owner.Name)) + } + + return owners + } +} diff --git a/internal/modules/ingressclass/get.go b/internal/modules/ingressclass/get.go deleted file mode 100644 index af61d3d9..00000000 --- a/internal/modules/ingressclass/get.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package ingressclass - -import ( - "net/http" - - "github.com/go-logr/logr" - "github.com/gorilla/mux" - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type get struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func Get(client client.Reader) modules.Module { - return &get{ - client: client, - log: ctrl.Log.WithName("ingressclass_get"), - gk: schema.GroupVersionKind{ - Group: networkingv1.GroupName, - Version: "*", - Kind: "ingressclasses", - }, - } -} - -func (g get) GroupVersionKind() schema.GroupVersionKind { - return g.gk -} - -func (g get) GroupKind() schema.GroupKind { - return g.gk.GroupKind() -} - -func (g get) Path() string { - return "/apis/networking.k8s.io/{version}/{endpoint:ingressclasses}/{name}" -} - -func (g get) Methods() []string { - return []string{} -} - -func (g get) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - name := mux.Vars(httpRequest)["name"] - - _, exactMatch, regexMatch, requirements := getIngressClasses(httpRequest, proxyTenants) - if len(requirements) > 0 { - ic, errIc := getIngressClassFromRequest(httpRequest) - if errIc != nil { - return nil, errors.NewBadRequest(errIc, g.GroupKind()) - } - - return utils.HandleGetSelector(httpRequest.Context(), ic, g.client, requirements, name, g.GroupKind()) - } - - icl, err := getIngressClassListFromRequest(httpRequest) - if err != nil { - return nil, errors.NewBadRequest(err, g.GroupKind()) - } - - if err = g.client.List(httpRequest.Context(), icl, client.MatchingLabels{corev1.LabelMetadataName: name}); err != nil { - return nil, errors.NewBadRequest(err, g.GroupKind()) - } - - var r *labels.Requirement - - if r, err = getIngressClassSelector(icl, exactMatch, regexMatch); err == nil { - return labels.NewSelector().Add(*r), nil - } - - switch httpRequest.Method { - case http.MethodGet: - return nil, errors.NewNotFoundError(name, g.GroupKind()) - default: - return nil, nil - } -} diff --git a/internal/modules/ingressclass/list.go b/internal/modules/ingressclass/list.go deleted file mode 100644 index 2291ab6d..00000000 --- a/internal/modules/ingressclass/list.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package ingressclass - -import ( - "github.com/go-logr/logr" - networkingv1 "k8s.io/api/networking/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/selection" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type list struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func List(client client.Reader) modules.Module { - return &list{ - client: client, - log: ctrl.Log.WithName("ingressclass_list"), - gk: schema.GroupVersionKind{ - Group: networkingv1.GroupName, - Version: "*", - Kind: "ingressclasses", - }, - } -} - -func (l list) GroupVersionKind() schema.GroupVersionKind { - return l.gk -} - -func (l list) GroupKind() schema.GroupKind { - return l.gk.GroupKind() -} - -func (l list) Path() string { - return "/apis/networking.k8s.io/{version}/{endpoint:ingressclasses/?}" -} - -func (l list) Methods() []string { - return []string{} -} - -func (l list) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - allowed, exactMatch, regexMatch, selectorsMatch := getIngressClasses(httpRequest, proxyTenants) - if len(selectorsMatch) > 0 { - return utils.HandleListSelector(selectorsMatch) - } - - icl, err := getIngressClassListFromRequest(httpRequest) - if err != nil { - return nil, errors.NewBadRequest(err, l.GroupKind()) - } - - if err = l.client.List(httpRequest.Context(), icl); err != nil { - return nil, errors.NewBadRequest(err, l.GroupKind()) - } - - var r *labels.Requirement - - if r, err = getIngressClassSelector(icl, exactMatch, regexMatch); err != nil { - if !allowed { - return nil, errors.NewNotAllowed(l.GroupKind()) - } - - r, _ = labels.NewRequirement("dontexistsignoreme", selection.Exists, []string{}) - } - - return labels.NewSelector().Add(*r), nil -} diff --git a/internal/modules/ingressclass/utils.go b/internal/modules/ingressclass/utils.go deleted file mode 100644 index 3d02bcbf..00000000 --- a/internal/modules/ingressclass/utils.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package ingressclass - -import ( - "fmt" - "net/http" - "regexp" - "sort" - - "github.com/gorilla/mux" - capsuleapi "github.com/projectcapsule/capsule/pkg/api" - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - networkingv1beta1 "k8s.io/api/networking/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -func getIngressClasses(request *http.Request, proxyTenants []*tenant.ProxyTenant) (allowed bool, exact []string, regex []*regexp.Regexp, requirements []labels.Requirement) { - requirements = []labels.Requirement{} - - for _, pt := range proxyTenants { - if ok := pt.RequestAllowed(request, capsuleapi.IngressClassesProxy); !ok { - continue - } - - allowed = true - - ic := pt.Tenant.Spec.IngressOptions.AllowedClasses - if ic == nil { - continue - } - - if len(ic.Exact) > 0 { - exact = append(exact, ic.Exact...) - } - - if len(ic.Default) > 0 { - exact = append(exact, ic.Default) - } - - //nolint:staticcheck - if r := ic.Regex; len(r) > 0 { - regex = append(regex, regexp.MustCompile(r)) - } - - selector, err := metav1.LabelSelectorAsSelector(&ic.LabelSelector) - if err != nil { - continue - } - - reqs, selectable := selector.Requirements() - if !selectable { - continue - } - - requirements = append(requirements, reqs...) - } - - sort.SliceStable(exact, func(i, _ int) bool { - return exact[i] < exact[0] - }) - - return allowed, exact, regex, requirements -} - -func getIngressClassListFromRequest(request *http.Request) (ic client.ObjectList, err error) { - v := mux.Vars(request)["version"] - switch v { - case networkingv1.SchemeGroupVersion.Version: - ic = &networkingv1.IngressClassList{} - case networkingv1beta1.SchemeGroupVersion.Version: - ic = &networkingv1beta1.IngressClassList{} - default: - return nil, fmt.Errorf("ingressClass %s is not supported", v) - } - - return -} - -func getIngressClassFromRequest(request *http.Request) (ic client.Object, err error) { - v := mux.Vars(request)["version"] - switch v { - case "v1": - ic = &networkingv1.IngressClass{} - case "v1beta1": - ic = &networkingv1beta1.IngressClass{} - default: - return nil, fmt.Errorf("ingressClass %s is not supported", v) - } - - return -} - -func getIngressClassSelector(sc client.ObjectList, exact []string, regex []*regexp.Regexp) (*labels.Requirement, error) { - isIngressClassRegexed := func(name string, regex []*regexp.Regexp) bool { - for _, r := range regex { - if r.MatchString(name) { - return true - } - } - - return false - } - - var names []string - - switch t := sc.(type) { - case *networkingv1beta1.IngressClassList: - for _, i := range t.Items { - if isIngressClassRegexed(i.GetName(), regex) { - names = append(names, i.GetName()) - - continue - } - - if f := sort.SearchStrings(exact, i.GetName()); f < len(exact) && exact[f] == i.GetName() { - names = append(names, i.GetName()) - } - } - case *networkingv1.IngressClassList: - for _, i := range t.Items { - if isIngressClassRegexed(i.GetName(), regex) { - names = append(names, i.GetName()) - - continue - } - - if f := sort.SearchStrings(exact, i.GetName()); f < len(exact) && exact[f] == i.GetName() { - names = append(names, i.GetName()) - } - } - } - - if len(names) > 0 { - return labels.NewRequirement(corev1.LabelMetadataName, selection.In, names) - } - - return nil, fmt.Errorf("cannot create LabelSelector for the requested IngressClass requirement") -} diff --git a/internal/modules/lease/get.go b/internal/modules/lease/get.go deleted file mode 100644 index 85c2e859..00000000 --- a/internal/modules/lease/get.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package lease - -import ( - "github.com/go-logr/logr" - "github.com/gorilla/mux" - capsuleapi "github.com/projectcapsule/capsule/pkg/api" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type get struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func Get(client client.Reader) modules.Module { - return &get{ - client: client, - log: ctrl.Log.WithName("node_get"), - gk: schema.GroupVersionKind{ - Group: corev1.GroupName, - Version: "*", - Kind: "nodes", - }, - } -} - -func (g get) GroupVersionKind() schema.GroupVersionKind { - return g.gk -} - -func (g get) GroupKind() schema.GroupKind { - return g.gk.GroupKind() -} - -func (g get) Path() string { - return "/apis/coordination.k8s.io/v1/namespaces/kube-node-lease/leases/{name}" -} - -func (g get) Methods() []string { - return []string{"get"} -} - -func (g get) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - var selectors []map[string]string - - httpRequest := proxyRequest.GetHTTPRequest() - - for _, pt := range proxyTenants { - if ok := pt.RequestAllowed(httpRequest, capsuleapi.NodesProxy); ok { - selectors = append(selectors, pt.Tenant.Spec.NodeSelector) - } - } - - name := mux.Vars(httpRequest)["name"] - - node := &corev1.Node{} - //nolint:nilerr - if err = g.client.Get(httpRequest.Context(), types.NamespacedName{Name: name}, node); err != nil { - // offload failure to Kubernetes API due to missing RBAC - return nil, nil - } - - for _, sel := range selectors { - for k := range sel { - if sel[k] == node.GetLabels()[k] { - // We're matching the nodeSelector of the Tenant: - // adding an empty selector in order to decorate the request - return labels.NewSelector().Add(), nil - } - } - } - // requesting lease for a non owner Node: let Kubernetes deal with it - return nil, nil -} diff --git a/internal/modules/persistentvolume/get.go b/internal/modules/persistentvolume/get.go deleted file mode 100644 index a1970791..00000000 --- a/internal/modules/persistentvolume/get.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package persistentvolume - -import ( - "github.com/go-logr/logr" - "github.com/gorilla/mux" - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type get struct { - client client.Reader - log logr.Logger - labelKey string - gk schema.GroupVersionKind -} - -func Get(client client.Reader) modules.Module { - label, _ := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{}) - - return &get{ - client: client, - log: ctrl.Log.WithName("persistentvolume_get"), - labelKey: label, - gk: schema.GroupVersionKind{ - Group: corev1.GroupName, - Version: "*", - Kind: "persistentvolumes", - }, - } -} - -func (g get) GroupVersionKind() schema.GroupVersionKind { - return g.gk -} - -func (g get) GroupKind() schema.GroupKind { - return g.gk.GroupKind() -} - -func (g get) Path() string { - return "/api/v1/{endpoint:persistentvolumes}/{name}" -} - -func (g get) Methods() []string { - return []string{} -} - -func (g get) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - name := mux.Vars(httpRequest)["name"] - - _, requirement := getPersistentVolume(httpRequest, proxyTenants, g.labelKey) - - rc := &corev1.PersistentVolume{} - - return utils.HandleGetSelector(httpRequest.Context(), rc, g.client, []labels.Requirement{requirement}, name, g.GroupKind()) -} diff --git a/internal/modules/persistentvolume/list.go b/internal/modules/persistentvolume/list.go deleted file mode 100644 index 036426c5..00000000 --- a/internal/modules/persistentvolume/list.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package persistentvolume - -import ( - "github.com/go-logr/logr" - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type list struct { - client client.Reader - log logr.Logger - labelKey string - gk schema.GroupVersionKind -} - -func List(client client.Reader) modules.Module { - label, _ := capsulev1beta2.GetTypeLabel(&capsulev1beta2.Tenant{}) - - return &list{ - client: client, - log: ctrl.Log.WithName("persistentvolume_list"), - labelKey: label, - gk: schema.GroupVersionKind{ - Group: corev1.GroupName, - Version: "*", - Kind: "persistentvolumes", - }, - } -} - -func (l list) GroupVersionKind() schema.GroupVersionKind { - return l.gk -} - -func (l list) GroupKind() schema.GroupKind { - return l.gk.GroupKind() -} - -func (l list) Path() string { - return "/api/v1/{endpoint:persistentvolumes/?}" -} - -func (l list) Methods() []string { - return []string{} -} - -func (l list) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - allowed, requirement := getPersistentVolume(httpRequest, proxyTenants, l.labelKey) - if !allowed { - return nil, errors.NewNotAllowed(l.GroupKind()) - } - - return utils.HandleListSelector([]labels.Requirement{requirement}) -} diff --git a/internal/modules/persistentvolume/utils.go b/internal/modules/persistentvolume/utils.go deleted file mode 100644 index b27725aa..00000000 --- a/internal/modules/persistentvolume/utils.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package persistentvolume - -import ( - "net/http" - - capsuleapi "github.com/projectcapsule/capsule/pkg/api" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -func getPersistentVolume(req *http.Request, proxyTenants []*tenant.ProxyTenant, label string) (allowed bool, requirements labels.Requirement) { - var tenantNames []string - - for _, pt := range proxyTenants { - if ok := pt.RequestAllowed(req, capsuleapi.PersistentVolumesProxy); ok { - allowed = true - - tenantNames = append(tenantNames, pt.Tenant.Name) - } - } - - requirement, _ := labels.NewRequirement(label, selection.In, tenantNames) - - return allowed, *requirement -} diff --git a/internal/modules/pod/get.go b/internal/modules/pod/get.go deleted file mode 100644 index b2c96579..00000000 --- a/internal/modules/pod/get.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package pod - -import ( - "github.com/go-logr/logr" - capsuleapi "github.com/projectcapsule/capsule/pkg/api" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -// get is the module that is going to be used when a `kubectl describe node` is issued by a Tenant owner. -// No other verbs are considered here, just the listing of Pods for the given node. -type get struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func Get(client client.Reader) modules.Module { - return &get{ - client: client, - log: ctrl.Log.WithName("node_get"), - gk: schema.GroupVersionKind{ - Group: corev1.GroupName, - Version: "*", - Kind: "nodes", - }, - } -} - -func (g get) GroupVersionKind() schema.GroupVersionKind { - return g.gk -} - -func (g get) GroupKind() schema.GroupKind { - return g.gk.GroupKind() -} - -func (g get) Path() string { - return "/api/v1/pods" -} - -func (g get) Methods() []string { - return []string{"get"} -} - -func (g get) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - rawFieldSelector, ok := httpRequest.URL.Query()["fieldSelector"] - // we want to process just the requests that are required by the kubectl describe feature and these contain the - // field selector in the query string: if it's not there, we can skip the processing. - if !ok || len(rawFieldSelector) == 0 { - return nil, nil - } - - var fieldSelector labels.Selector - - //nolint:nilerr - if fieldSelector, err = labels.Parse(rawFieldSelector[0]); err != nil { - // not valid labels, offloading Kubernetes to deal with the failure - return nil, nil - } - - var name string - - requirements, _ := fieldSelector.Requirements() - - for _, requirement := range requirements { - if requirement.Key() == "spec.nodeName" { - name = requirement.Values().List()[0] - - break - } - } - // the field selector is not matching any node, let Kubernetes deal the failure due to missing RBAC - if len(name) == 0 { - return nil, nil - } - - var selectors []map[string]string - // Ensuring the Tenant Owner can deal with the node listing - for _, pt := range proxyTenants { - if ok = pt.RequestAllowed(httpRequest, capsuleapi.NodesProxy); ok { - selectors = append(selectors, pt.Tenant.Spec.NodeSelector) - } - } - - node := &corev1.Node{} - if err = g.client.Get(httpRequest.Context(), types.NamespacedName{Name: name}, node); err != nil { - return nil, errors.NewBadRequest(err, g.GroupKind()) - } - - for _, sel := range selectors { - for k := range sel { - // If the node matches the label, adding an empty selector in order to decorate the request - if sel[k] == node.GetLabels()[k] { - return labels.NewSelector().Add(), nil - } - } - } - // offload to Kubernetes that will return the failure due to missing RBAC - return nil, nil -} diff --git a/internal/modules/priorityclass/get.go b/internal/modules/priorityclass/get.go deleted file mode 100644 index d750a9c3..00000000 --- a/internal/modules/priorityclass/get.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package priorityclass - -import ( - "net/http" - - "github.com/go-logr/logr" - "github.com/gorilla/mux" - corev1 "k8s.io/api/core/v1" - schedulingv1 "k8s.io/api/scheduling/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type get struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func Get(client client.Reader) modules.Module { - return &get{ - client: client, - log: ctrl.Log.WithName("priorityclass_get"), - gk: schema.GroupVersionKind{ - Group: schedulingv1.GroupName, - Version: "*", - Kind: "priorityclasses", - }, - } -} - -func (g get) GroupVersionKind() schema.GroupVersionKind { - return g.gk -} - -func (g get) GroupKind() schema.GroupKind { - return g.gk.GroupKind() -} - -func (g get) Path() string { - return "/apis/scheduling.k8s.io/v1/{endpoint:priorityclasses}/{name}" -} - -func (g get) Methods() []string { - return []string{} -} - -func (g get) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - name := mux.Vars(httpRequest)["name"] - - _, exactMatch, regexMatch, requirements := getPriorityClass(httpRequest, proxyTenants) - if len(requirements) > 0 { - pc := &schedulingv1.PriorityClass{} - - return utils.HandleGetSelector(httpRequest.Context(), pc, g.client, requirements, name, g.GroupKind()) - } - - sc := &schedulingv1.PriorityClassList{} - if err = g.client.List(httpRequest.Context(), sc, client.MatchingLabels{corev1.LabelMetadataName: name}); err != nil { - return nil, errors.NewBadRequest(err, g.GroupKind()) - } - - var r *labels.Requirement - - r, err = getPriorityClassSelector(sc, exactMatch, regexMatch) - - switch { - case err == nil: - return labels.NewSelector().Add(*r), nil - case httpRequest.Method == http.MethodGet: - return nil, errors.NewNotFoundError(name, g.GroupKind()) - default: - return nil, nil - } -} diff --git a/internal/modules/priorityclass/list.go b/internal/modules/priorityclass/list.go deleted file mode 100644 index 491c383a..00000000 --- a/internal/modules/priorityclass/list.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package priorityclass - -import ( - "github.com/go-logr/logr" - schedulingv1 "k8s.io/api/scheduling/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/selection" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type list struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func List(client client.Reader) modules.Module { - return &list{ - client: client, - log: ctrl.Log.WithName("priorityclass_list"), - gk: schema.GroupVersionKind{ - Group: schedulingv1.GroupName, - Version: "*", - Kind: "priorityclasses", - }, - } -} - -func (l list) GroupVersionKind() schema.GroupVersionKind { - return l.gk -} - -func (l list) GroupKind() schema.GroupKind { - return l.gk.GroupKind() -} - -func (l list) Path() string { - return "/apis/scheduling.k8s.io/v1/{endpoint:priorityclasses/?}" -} - -func (l list) Methods() []string { - return []string{} -} - -func (l list) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - allowed, exactMatch, regexMatch, selectorsMatch := getPriorityClass(httpRequest, proxyTenants) - if len(selectorsMatch) > 0 { - return utils.HandleListSelector(selectorsMatch) - } - - // Regex Deprecated, Therefor handeled last - sc := &schedulingv1.PriorityClassList{} - if err = l.client.List(httpRequest.Context(), sc); err != nil { - return nil, errors.NewBadRequest(err, l.GroupKind()) - } - - var r *labels.Requirement - - if r, err = getPriorityClassSelector(sc, exactMatch, regexMatch); err != nil { - if !allowed { - return nil, errors.NewNotAllowed(l.GroupKind()) - } - - r, _ = labels.NewRequirement("dontexistsignoreme", selection.Exists, []string{}) - } - - return labels.NewSelector().Add(*r), nil -} diff --git a/internal/modules/priorityclass/utils.go b/internal/modules/priorityclass/utils.go deleted file mode 100644 index 126536c0..00000000 --- a/internal/modules/priorityclass/utils.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package priorityclass - -import ( - "fmt" - "net/http" - "regexp" - "sort" - - capsuleapi "github.com/projectcapsule/capsule/pkg/api" - corev1 "k8s.io/api/core/v1" - schedulingv1 "k8s.io/api/scheduling/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -func getPriorityClass(req *http.Request, proxyTenants []*tenant.ProxyTenant) (allowed bool, exact []string, regex []*regexp.Regexp, requirements []labels.Requirement) { - requirements = []labels.Requirement{} - - for _, pt := range proxyTenants { - if ok := pt.RequestAllowed(req, capsuleapi.PriorityClassesProxy); !ok { - continue - } - - allowed = true - - pc := pt.Tenant.Spec.PriorityClasses - if pc == nil { - continue - } - - if len(pc.Exact) > 0 { - exact = append(exact, pc.Exact...) - } - - if len(pc.Default) > 0 { - exact = append(exact, pc.Default) - } - - //nolint:staticcheck - if r := pc.Regex; len(r) > 0 { - regex = append(regex, regexp.MustCompile(r)) - } - - selector, err := metav1.LabelSelectorAsSelector(&pc.LabelSelector) - if err != nil { - continue - } - - reqs, selectable := selector.Requirements() - if !selectable { - continue - } - - requirements = append(requirements, reqs...) - } - - sort.SliceStable(exact, func(i, _ int) bool { - return exact[i] < exact[0] - }) - - return allowed, exact, regex, requirements -} - -func getPriorityClassSelector(classes *schedulingv1.PriorityClassList, exact []string, regex []*regexp.Regexp) (*labels.Requirement, error) { - isPriorityClassRegexed := func(name string, regex []*regexp.Regexp) bool { - for _, r := range regex { - if r.MatchString(name) { - return true - } - } - - return false - } - - var names []string - - for _, s := range classes.Items { - if isPriorityClassRegexed(s.GetName(), regex) { - names = append(names, s.GetName()) - - continue - } - - if f := sort.SearchStrings(exact, s.GetName()); f < len(exact) && exact[f] == s.GetName() { - names = append(names, s.GetName()) - } - } - - if len(names) > 0 { - return labels.NewRequirement(corev1.LabelMetadataName, selection.In, names) - } - - return nil, fmt.Errorf("cannot create LabelSelector for the requested PriorityClass requirement") -} diff --git a/internal/modules/runtimeclass/get.go b/internal/modules/runtimeclass/get.go deleted file mode 100644 index 20688940..00000000 --- a/internal/modules/runtimeclass/get.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package runtimeclass - -import ( - "github.com/go-logr/logr" - "github.com/gorilla/mux" - nodev1 "k8s.io/api/node/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type get struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func Get(client client.Reader) modules.Module { - return &get{ - client: client, - log: ctrl.Log.WithName("runtimeclass_get"), - gk: schema.GroupVersionKind{ - Group: nodev1.GroupName, - Version: "*", - Kind: "runtimeclasses", - }, - } -} - -func (g get) GroupVersionKind() schema.GroupVersionKind { - return g.gk -} - -func (g get) GroupKind() schema.GroupKind { - return g.gk.GroupKind() -} - -func (g get) Path() string { - return "/apis/node.k8s.io/v1/{endpoint:runtimeclasses}/{name}" -} - -func (g get) Methods() []string { - return []string{} -} - -func (g get) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - name := mux.Vars(httpRequest)["name"] - - _, requirements := getRuntimeClass(httpRequest, proxyTenants) - if len(requirements) == 0 { - return nil, errors.NewNotFoundError(name, g.GroupKind()) - } - - rc := &nodev1.RuntimeClass{} - - return utils.HandleGetSelector(httpRequest.Context(), rc, g.client, requirements, name, g.GroupKind()) -} diff --git a/internal/modules/runtimeclass/list.go b/internal/modules/runtimeclass/list.go deleted file mode 100644 index ba2cd591..00000000 --- a/internal/modules/runtimeclass/list.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package runtimeclass - -import ( - "github.com/go-logr/logr" - nodev1 "k8s.io/api/node/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/selection" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type list struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func List(client client.Reader) modules.Module { - return &list{ - client: client, - log: ctrl.Log.WithName("runtimeclass_list"), - gk: schema.GroupVersionKind{ - Group: nodev1.GroupName, - Version: "*", - Kind: "runtimeclasses", - }, - } -} - -func (l list) GroupVersionKind() schema.GroupVersionKind { - return l.gk -} - -func (l list) GroupKind() schema.GroupKind { - return l.gk.GroupKind() -} - -func (l list) Path() string { - return "/apis/node.k8s.io/v1/{endpoint:runtimeclasses/?}" -} - -func (l list) Methods() []string { - return []string{} -} - -func (l list) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - allowed, selectorsMatch := getRuntimeClass(httpRequest, proxyTenants) - - if !allowed { - return nil, errors.NewNotAllowed(l.GroupKind()) - } - - if len(selectorsMatch) == 0 { - r, _ := labels.NewRequirement("dontexistsignoreme", selection.Exists, []string{}) - - return labels.NewSelector().Add(*r), nil - } - - return utils.HandleListSelector(selectorsMatch) -} diff --git a/internal/modules/runtimeclass/utils.go b/internal/modules/runtimeclass/utils.go deleted file mode 100644 index 29a76c46..00000000 --- a/internal/modules/runtimeclass/utils.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package runtimeclass - -import ( - "net/http" - - capsuleapi "github.com/projectcapsule/capsule/pkg/api" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -func getRuntimeClass(req *http.Request, proxyTenants []*tenant.ProxyTenant) (allowed bool, requirements []labels.Requirement) { - requirements = []labels.Requirement{} - - for _, pt := range proxyTenants { - if ok := pt.RequestAllowed(req, capsuleapi.RuntimeClassesProxy); ok { - allowed = true - - rc := pt.Tenant.Spec.RuntimeClasses - if rc == nil { - continue - } - - selector, err := metav1.LabelSelectorAsSelector(&rc.LabelSelector) - if err != nil { - continue - } - - reqs, selectable := selector.Requirements() - if !selectable { - continue - } - - requirements = append(requirements, reqs...) - } - } - - return allowed, requirements -} diff --git a/internal/modules/storageclass/get.go b/internal/modules/storageclass/get.go deleted file mode 100644 index 00faa280..00000000 --- a/internal/modules/storageclass/get.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package storageclass - -import ( - "net/http" - - "github.com/go-logr/logr" - "github.com/gorilla/mux" - corev1 "k8s.io/api/core/v1" - storagev1 "k8s.io/api/storage/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type get struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func Get(client client.Reader) modules.Module { - return &get{ - client: client, - log: ctrl.Log.WithName("storageclass_get"), - gk: schema.GroupVersionKind{ - Group: storagev1.GroupName, - Version: "*", - Kind: "storageclasses", - }, - } -} - -func (g get) GroupVersionKind() schema.GroupVersionKind { - return g.gk -} - -func (g get) GroupKind() schema.GroupKind { - return g.gk.GroupKind() -} - -func (g get) Path() string { - return "/apis/storage.k8s.io/v1/{endpoint:storageclasses}/{name}" -} - -func (g get) Methods() []string { - return []string{} -} - -func (g get) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - name := mux.Vars(httpRequest)["name"] - - _, exactMatch, regexMatch, requirements := getStorageClasses(httpRequest, proxyTenants) - if len(requirements) > 0 { - sc := &storagev1.StorageClass{} - - return utils.HandleGetSelector(httpRequest.Context(), sc, g.client, requirements, name, g.GroupKind()) - } - - sc := &storagev1.StorageClassList{} - if err = g.client.List(httpRequest.Context(), sc, client.MatchingLabels{corev1.LabelMetadataName: name}); err != nil { - return nil, errors.NewBadRequest(err, g.GroupKind()) - } - - var r *labels.Requirement - - r, err = getStorageClassSelector(sc, exactMatch, regexMatch) - - switch { - case err == nil: - return labels.NewSelector().Add(*r), nil - case httpRequest.Method == http.MethodGet: - return nil, errors.NewNotFoundError(name, g.GroupKind()) - default: - return nil, nil - } -} diff --git a/internal/modules/storageclass/list.go b/internal/modules/storageclass/list.go deleted file mode 100644 index 5283fb51..00000000 --- a/internal/modules/storageclass/list.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package storageclass - -import ( - "github.com/go-logr/logr" - storagev1 "k8s.io/api/storage/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/selection" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/projectcapsule/capsule-proxy/internal/modules" - "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/utils" - "github.com/projectcapsule/capsule-proxy/internal/request" - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -type list struct { - client client.Reader - log logr.Logger - gk schema.GroupVersionKind -} - -func List(client client.Reader) modules.Module { - return &list{ - client: client, - log: ctrl.Log.WithName("storageclass_list"), - gk: schema.GroupVersionKind{ - Group: storagev1.GroupName, - Version: "*", - Kind: "storageclasses", - }, - } -} - -func (l list) GroupVersionKind() schema.GroupVersionKind { - return l.gk -} - -func (l list) GroupKind() schema.GroupKind { - return l.gk.GroupKind() -} - -func (l list) Path() string { - return "/apis/storage.k8s.io/v1/{endpoint:storageclasses/?}" -} - -func (l list) Methods() []string { - return []string{} -} - -func (l list) Handle(proxyTenants []*tenant.ProxyTenant, proxyRequest request.Request) (selector labels.Selector, err error) { - httpRequest := proxyRequest.GetHTTPRequest() - - allowed, exactMatch, regexMatch, selectorsMatch := getStorageClasses(httpRequest, proxyTenants) - if len(selectorsMatch) > 0 { - return utils.HandleListSelector(selectorsMatch) - } - - sc := &storagev1.StorageClassList{} - if err = l.client.List(httpRequest.Context(), sc); err != nil { - return nil, errors.NewBadRequest(err, l.GroupKind()) - } - - var r *labels.Requirement - - if r, err = getStorageClassSelector(sc, exactMatch, regexMatch); err != nil { - if !allowed { - return nil, errors.NewNotAllowed(l.GroupKind()) - } - - r, _ = labels.NewRequirement("dontexistsignoreme", selection.Exists, []string{}) - } - - return labels.NewSelector().Add(*r), nil -} diff --git a/internal/modules/storageclass/utils.go b/internal/modules/storageclass/utils.go deleted file mode 100644 index 5a759f1a..00000000 --- a/internal/modules/storageclass/utils.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package storageclass - -import ( - "fmt" - "net/http" - "regexp" - "sort" - - capsuleapi "github.com/projectcapsule/capsule/pkg/api" - corev1 "k8s.io/api/core/v1" - storagev1 "k8s.io/api/storage/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - - "github.com/projectcapsule/capsule-proxy/internal/tenant" -) - -func getStorageClasses(req *http.Request, proxyTenants []*tenant.ProxyTenant) (allowed bool, exact []string, regex []*regexp.Regexp, requirements []labels.Requirement) { - requirements = []labels.Requirement{} - - for _, pt := range proxyTenants { - if ok := pt.RequestAllowed(req, capsuleapi.StorageClassesProxy); !ok { - continue - } - - allowed = true - - sc := pt.Tenant.Spec.StorageClasses - if sc == nil { - continue - } - - if len(sc.Exact) > 0 { - exact = append(exact, sc.Exact...) - } - - if len(sc.Default) > 0 { - exact = append(exact, sc.Default) - } - - //nolint:staticcheck - if r := sc.Regex; len(r) > 0 { - regex = append(regex, regexp.MustCompile(r)) - } - - selector, err := metav1.LabelSelectorAsSelector(&sc.LabelSelector) - if err != nil { - continue - } - - reqs, selectable := selector.Requirements() - if !selectable { - continue - } - - requirements = append(requirements, reqs...) - } - - sort.SliceStable(exact, func(i, _ int) bool { - return exact[i] < exact[0] - }) - - return allowed, exact, regex, requirements -} - -func getStorageClassSelector(classes *storagev1.StorageClassList, exact []string, regex []*regexp.Regexp) (*labels.Requirement, error) { - isStorageClassRegexed := func(name string, regex []*regexp.Regexp) bool { - for _, r := range regex { - if r.MatchString(name) { - return true - } - } - - return false - } - - var names []string - - for _, s := range classes.Items { - if isStorageClassRegexed(s.GetName(), regex) { - names = append(names, s.GetName()) - - continue - } - - if f := sort.SearchStrings(exact, s.GetName()); f < len(exact) && exact[f] == s.GetName() { - names = append(names, s.GetName()) - } - } - - if len(names) > 0 { - return labels.NewRequirement(corev1.LabelMetadataName, selection.In, names) - } - - return nil, fmt.Errorf("cannot create LabelSelector for the requested StorageClass requirement") -} diff --git a/internal/modules/utils/node.go b/internal/modules/utils/node.go index bf26db04..8814fd15 100644 --- a/internal/modules/utils/node.go +++ b/internal/modules/utils/node.go @@ -7,7 +7,6 @@ import ( "fmt" "net/http" - capsuleapi "github.com/projectcapsule/capsule/pkg/api" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" @@ -41,11 +40,9 @@ func GetNodeSelector(nl *corev1.NodeList, selectors []map[string]string) (*label return nil, fmt.Errorf("cannot create LabelSelector for the requested Node requirement") } -func GetNodeSelectors(request *http.Request, proxyTenants []*tenant.ProxyTenant) (selectors []map[string]string) { +func GetNodeSelectors(_ *http.Request, proxyTenants []*tenant.ProxyTenant) (selectors []map[string]string) { for _, pt := range proxyTenants { - if ok := pt.RequestAllowed(request, capsuleapi.NodesProxy); ok { - selectors = append(selectors, pt.Tenant.Spec.NodeSelector) - } + selectors = append(selectors, pt.Tenant.Spec.NodeSelector) } return diff --git a/internal/tenant/operations.go b/internal/tenant/operations.go deleted file mode 100644 index 1b96d2ae..00000000 --- a/internal/tenant/operations.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package tenant - -import ( - "net/http" - - capsuleapi "github.com/projectcapsule/capsule/pkg/api" -) - -type Operations struct { - List bool - Update bool - Delete bool -} - -func defaultOperations() *Operations { - return &Operations{ - List: false, - Update: false, - Delete: false, - } -} - -func (o *Operations) Allow(operation capsuleapi.ProxyOperation) { - switch operation { - case capsuleapi.ListOperation: - o.List = true - case capsuleapi.UpdateOperation: - o.Update = true - case capsuleapi.DeleteOperation: - o.Delete = true - } -} - -func (o *Operations) IsAllowed(request *http.Request) (ok bool) { - switch request.Method { - case http.MethodGet: - ok = o.List - case http.MethodPut, http.MethodPatch: - ok = o.List - ok = ok && o.Update - case http.MethodDelete: - ok = o.List - ok = ok && o.Delete - default: - break - } - - return -} diff --git a/internal/tenant/proxytenant.go b/internal/tenant/proxytenant.go index 87a3c373..20399df5 100644 --- a/internal/tenant/proxytenant.go +++ b/internal/tenant/proxytenant.go @@ -4,8 +4,6 @@ package tenant import ( - "net/http" - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" capsuleapi "github.com/projectcapsule/capsule/pkg/api" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -15,51 +13,26 @@ import ( type ProxyTenant struct { Tenant capsulev1beta2.Tenant - ProxySetting map[capsuleapi.ProxyServiceKind]*Operations ClusterResources []v1beta1.ClusterResource } -func defaultProxySettings() map[capsuleapi.ProxyServiceKind]*Operations { - return map[capsuleapi.ProxyServiceKind]*Operations{ - capsuleapi.NodesProxy: defaultOperations(), - capsuleapi.StorageClassesProxy: defaultOperations(), - capsuleapi.IngressClassesProxy: defaultOperations(), - capsuleapi.PriorityClassesProxy: defaultOperations(), - capsuleapi.RuntimeClassesProxy: defaultOperations(), - capsuleapi.PersistentVolumesProxy: defaultOperations(), - } -} - -func NewProxyTenant(ownerName string, ownerKind capsuleapi.OwnerKind, tenant capsulev1beta2.Tenant, owners []v1beta1.OwnerSpec) *ProxyTenant { - var ( - tenantProxySettings []capsuleapi.ProxySettings - tenantClusterResources []v1beta1.ClusterResource - ) +func NewProxyTenant(tenant capsulev1beta2.Tenant, ownerName string, ownerKind capsuleapi.OwnerKind, owners []v1beta1.OwnerSpec) *ProxyTenant { + var tenantClusterResources []v1beta1.ClusterResource for _, owner := range owners { if owner.Name == ownerName && owner.Kind == ownerKind { - tenantProxySettings = owner.ProxyOperations tenantClusterResources = owner.ClusterResources } } - proxySettings := defaultProxySettings() - - for _, setting := range tenantProxySettings { - for _, operation := range setting.Operations { - proxySettings[setting.Kind].Allow(operation) - } - } - return &ProxyTenant{ Tenant: tenant, - ProxySetting: proxySettings, ClusterResources: tenantClusterResources, } } -// This Function returns a ProxyTenant struct for GlobalProxySettings. These Settings are currently not bound to a tenant and therefor -// an empty tenant and empty ProxySettings are returned. +// NewClusterProxy returns a ProxyTenant struct for GlobalProxySettings. These settings are currently not bound to a tenant and therefore +// an empty tenant is returned. func NewClusterProxy(ownerName string, ownerKind capsuleapi.OwnerKind, owners []v1beta1.GlobalSubjectSpec) *ProxyTenant { var tenantClusterResources []v1beta1.ClusterResource @@ -78,11 +51,6 @@ func NewClusterProxy(ownerName string, ownerKind capsuleapi.OwnerKind, owners [] }, Spec: capsulev1beta2.TenantSpec{}, }, - ProxySetting: defaultProxySettings(), ClusterResources: tenantClusterResources, } } - -func (p *ProxyTenant) RequestAllowed(request *http.Request, serviceKind capsuleapi.ProxyServiceKind) (ok bool) { - return p.ProxySetting[serviceKind].IsAllowed(request) -} diff --git a/internal/webserver/webserver.go b/internal/webserver/webserver.go index ab02a583..18600a11 100644 --- a/internal/webserver/webserver.go +++ b/internal/webserver/webserver.go @@ -45,20 +45,14 @@ import ( "github.com/projectcapsule/capsule-proxy/api/v1beta1" "github.com/projectcapsule/capsule-proxy/internal/authorization" "github.com/projectcapsule/capsule-proxy/internal/controllers" - "github.com/projectcapsule/capsule-proxy/internal/features" "github.com/projectcapsule/capsule-proxy/internal/indexer" "github.com/projectcapsule/capsule-proxy/internal/modules" "github.com/projectcapsule/capsule-proxy/internal/modules/clusterscoped" moderrors "github.com/projectcapsule/capsule-proxy/internal/modules/errors" - "github.com/projectcapsule/capsule-proxy/internal/modules/ingressclass" "github.com/projectcapsule/capsule-proxy/internal/modules/metric" "github.com/projectcapsule/capsule-proxy/internal/modules/namespace" "github.com/projectcapsule/capsule-proxy/internal/modules/namespaced" "github.com/projectcapsule/capsule-proxy/internal/modules/node" - "github.com/projectcapsule/capsule-proxy/internal/modules/persistentvolume" - "github.com/projectcapsule/capsule-proxy/internal/modules/priorityclass" - "github.com/projectcapsule/capsule-proxy/internal/modules/runtimeclass" - "github.com/projectcapsule/capsule-proxy/internal/modules/storageclass" "github.com/projectcapsule/capsule-proxy/internal/modules/tenants" "github.com/projectcapsule/capsule-proxy/internal/options" req "github.com/projectcapsule/capsule-proxy/internal/request" @@ -356,7 +350,7 @@ func (n *kubeFilter) authorizationMiddleware(next http.Handler) http.Handler { n.log.Error(err, "cannot decode authorization object") } - err = authorization.MutateAuthorization(n.gates.Enabled(features.ProxyClusterScoped), proxyTenants, &obj, *gvk) + err = authorization.MutateAuthorization(proxyTenants, &obj, *gvk) if err != nil { n.log.Error(err, "cannot mutate authorization object") } @@ -458,49 +452,35 @@ func (n *kubeFilter) registerModules(ctx context.Context, root *mux.Router) { namespace.Get(n.roleBindingsReflector, n.reader), tenants.List(), tenants.Get(n.reader), + // Node and metric modules are kept as dedicated modules + // since they rely on Tenant.Spec.NodeSelector matching + node.List(n.reader), + node.Get(n.reader), + metric.Get(n.reader), + metric.List(n.reader), } // Discovery client discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(ctrl.GetConfigOrDie()) - // When the ProxyClusterScoped flag is enabled - // we are no longer respecting legacy proxysettings - if n.gates.Enabled(features.ProxyClusterScoped) { - apis, err := serverPreferredResources(discoveryClient) - if err != nil { - panic(err) - } + // Use the generic cluster scoped module for all remaining cluster-scoped resources. + // Resources already handled by dedicated modules above (namespaces, tenants, nodes, metrics) + // are skipped via moduleGroupKindPresent. + apis, err := serverPreferredResources(discoveryClient) + if err != nil { + panic(err) + } - for _, api := range apis { - if !moduleGroupKindPresent(modList, api) { - n.log.V(6).Info("adding generic cluster scoped resource", "url", api.Path()) - modList = append(modList, clusterscoped.List(n.reader, n.writer, api.Path())) - modList = append(modList, clusterscoped.Get(discoveryClient, n.reader, n.writer, api.ResourcePath())) - } + for _, api := range apis { + if !moduleGroupKindPresent(modList, api) { + n.log.V(6).Info("adding generic cluster scoped resource", "url", api.Path()) + modList = append(modList, clusterscoped.List(n.reader, n.writer, api.Path())) + modList = append(modList, clusterscoped.Get(discoveryClient, n.reader, n.writer, api.ResourcePath())) } - } else { - // Adds all legacy routes - modList = append(modList, []modules.Module{ - node.List(n.reader), - node.Get(n.reader), - ingressclass.List(n.reader), - ingressclass.Get(n.reader), - storageclass.Get(n.reader), - storageclass.List(n.reader), - priorityclass.List(n.reader), - priorityclass.Get(n.reader), - runtimeclass.Get(n.reader), - runtimeclass.List(n.reader), - persistentvolume.Get(n.reader), - persistentvolume.List(n.reader), - metric.Get(n.reader), - metric.List(n.reader), - }..., - ) } // Get all API group resources - apis, err := discoverAPI(ctrl.GetConfigOrDie()) + apis, err = discoverAPI(ctrl.GetConfigOrDie()) if err != nil { panic(err) } @@ -595,31 +575,14 @@ func (n *kubeFilter) getTenantsForOwner(ctx context.Context, username string, gr return } -func (n *kubeFilter) ownerFromCapsuleToProxySetting(owners capsuleapi.OwnerListSpec) []v1beta1.OwnerSpec { - out := make([]v1beta1.OwnerSpec, 0, len(owners)) - - for _, owner := range owners { - out = append(out, v1beta1.OwnerSpec{ - Kind: owner.Kind, - Name: owner.Name, - ProxyOperations: owner.ProxyOperations, - }) - } - - return out -} - //nolint:funlen func (n *kubeFilter) getProxyTenantsForOwnerKind(ctx context.Context, ownerKind capsuleapi.OwnerKind, ownerName string) (proxyTenants []*tenant.ProxyTenant, err error) { - //nolint:prealloc - var tenants []string - ownerIndexValue := fmt.Sprintf("%s:%s", ownerKind.String(), ownerName) tl := &capsulev1beta2.TenantList{} f := client.MatchingFields{ - ".spec.owner.ownerkind": ownerIndexValue, + indexer.TenantOwnerKindField: ownerIndexValue, } if err = n.managerReader.List(ctx, tl, f); err != nil { return nil, fmt.Errorf("cannot retrieve Tenants list: %w", err) @@ -646,29 +609,28 @@ func (n *kubeFilter) getProxyTenantsForOwnerKind(ctx context.Context, ownerKind continue } - proxyTenants = append(proxyTenants, tenant.NewProxyTenant(ownerName, ownerKind, tntList.Items[0], proxySetting.Spec.Subjects)) + proxyTenants = append(proxyTenants, tenant.NewProxyTenant(tntList.Items[0], ownerName, ownerKind, proxySetting.Spec.Subjects)) } // Consider Global ProxySettings - // Only consider GlobalProxySettings if the feature gate is enabled - if n.gates.Enabled(features.ProxyClusterScoped) { - globalProxySettings := &v1beta1.GlobalProxySettingsList{} - if err = n.managerReader.List(ctx, globalProxySettings, client.MatchingFields{indexer.GlobalKindField: ownerIndexValue}); err != nil { - n.log.Error(err, "cannot retrieve GlobalProxySettings", "owner", ownerKind, "name", ownerName) - } - // Convert GlobalProxySettings to TenantProxies - for _, globalProxySetting := range globalProxySettings.Items { - n.log.V(10).Info("Converting GlobalProxySettings", "Setting", globalProxySetting.Name) - - tProxy := tenant.NewClusterProxy(ownerName, ownerKind, globalProxySetting.Spec.Rules) - proxyTenants = append(proxyTenants, tProxy) - } + globalProxySettings := &v1beta1.GlobalProxySettingsList{} + if err = n.managerReader.List(ctx, globalProxySettings, client.MatchingFields{indexer.GlobalKindField: ownerIndexValue}); err != nil { + n.log.Error(err, "cannot retrieve GlobalProxySettings", "owner", ownerKind, "name", ownerName) + } + // Convert GlobalProxySettings to TenantProxies + for _, globalProxySetting := range globalProxySettings.Items { + n.log.V(10).Info("Converting GlobalProxySettings", "Setting", globalProxySetting.Name) - n.log.V(10).Info("Collected GlobalProxySettings", "owner", ownerKind, "name", ownerName, "settings", len(globalProxySettings.Items)) + tProxy := tenant.NewClusterProxy(ownerName, ownerKind, globalProxySetting.Spec.Rules) + proxyTenants = append(proxyTenants, tProxy) } + n.log.V(10).Info("Collected GlobalProxySettings", "owner", ownerKind, "name", ownerName, "settings", len(globalProxySettings.Items)) + + tenants := make([]string, 0, len(tl.Items)) + for _, t := range tl.Items { - proxyTenants = append(proxyTenants, tenant.NewProxyTenant(ownerName, ownerKind, t, n.ownerFromCapsuleToProxySetting(t.Spec.Owners))) + proxyTenants = append(proxyTenants, tenant.NewProxyTenant(t, ownerName, ownerKind, nil)) tenants = append(tenants, t.GetName()) } diff --git a/main.go b/main.go index e95b4f5f..806f6e27 100644 --- a/main.go +++ b/main.go @@ -77,12 +77,13 @@ func main() { LockToDefault: false, PreRelease: featuregate.Alpha, }, - features.ProxyClusterScoped: { + features.SkipImpersonationReview: { Default: false, LockToDefault: false, PreRelease: featuregate.Alpha, }, - features.SkipImpersonationReview: { + //nolint:staticcheck + features.ProxyClusterScoped: { Default: false, LockToDefault: false, PreRelease: featuregate.Alpha, @@ -249,12 +250,9 @@ First match is used and can be specified multiple times as comma separated value indexers := []capsuleindexer.CustomIndexer{ &tenant.NamespacesReference{Obj: &capsulev1beta2.Tenant{}}, - &tenant.OwnerReference{}, + &indexer.TenantOwnerReference{}, &indexer.ProxySetting{}, - } - // Optional Indexers - if gates.Enabled(features.ProxyClusterScoped) { - indexers = append(indexers, &indexer.GlobalProxySetting{}) + &indexer.GlobalProxySetting{}, } for _, fieldIndex := range indexers {