├── test
├── tools
│ ├── issuer
│ │ ├── .gitignore
│ │ ├── Dockerfile
│ │ ├── cmd
│ │ │ ├── main.go
│ │ │ └── options
│ │ │ │ └── options.go
│ │ └── pkg
│ │ │ └── issuer
│ │ │ └── issuer.go
│ ├── audit-webhook
│ │ ├── .gitignore
│ │ ├── Dockerfile
│ │ ├── cmd
│ │ │ ├── main.go
│ │ │ └── options
│ │ │ │ └── options.go
│ │ └── pkg
│ │ │ └── sink
│ │ │ └── sink.go
│ └── fake-apiserver
│ │ ├── .gitignore
│ │ ├── Dockerfile
│ │ ├── cmd
│ │ ├── options
│ │ │ └── options.go
│ │ └── main.go
│ │ └── pkg
│ │ └── server
│ │ └── server.go
├── util
│ ├── strings.go
│ └── tls.go
├── e2e
│ ├── framework
│ │ ├── helper
│ │ │ ├── helper.go
│ │ │ ├── requester.go
│ │ │ ├── kubectl.go
│ │ │ ├── secrets.go
│ │ │ └── token.go
│ │ ├── config
│ │ │ └── config.go
│ │ └── util.go
│ └── suite
│ │ ├── cases
│ │ ├── doc.go
│ │ ├── probe
│ │ │ └── probe.go
│ │ ├── token
│ │ │ └── token.go
│ │ └── headers
│ │ │ └── headers.go
│ │ ├── suite_test.go
│ │ └── suite.go
└── environment
│ └── environment.go
├── .dockerignore
├── pkg
├── mocks
│ ├── .gitignore
│ └── mocks.go
├── util
│ ├── port.go
│ ├── signals.go
│ ├── token.go
│ └── flags
│ │ ├── string_to_string_slice_test.go
│ │ └── string_to_string_slice.go
├── proxy
│ ├── audit
│ │ ├── handler.go
│ │ └── audit.go
│ ├── hooks
│ │ └── hooks.go
│ ├── tokenreview
│ │ ├── fake
│ │ │ └── tokenreview.go
│ │ ├── tokenreview.go
│ │ └── tokenreview_test.go
│ ├── logging
│ │ ├── accesslog_test.go
│ │ └── accesslog.go
│ ├── subjectaccessreview
│ │ └── fake
│ │ │ └── subjectaccessreview.go
│ └── context
│ │ └── context.go
└── probe
│ ├── probe.go
│ └── probe_test.go
├── demo
├── manifests
│ ├── .gitignore
│ ├── vendor
│ │ └── kube-prod-runtime
│ │ │ ├── VERSION
│ │ │ ├── components
│ │ │ ├── fluentd-es-config
│ │ │ │ ├── system.conf
│ │ │ │ ├── fluentd.conf
│ │ │ │ ├── output.conf
│ │ │ │ ├── monitoring.conf
│ │ │ │ └── import-from-upstream.py
│ │ │ ├── images.json
│ │ │ ├── elasticsearch-config
│ │ │ │ └── java.security
│ │ │ ├── alertmanager-config.jsonnet
│ │ │ ├── version.jsonnet
│ │ │ ├── oauth2-proxy.jsonnet
│ │ │ ├── externaldns.jsonnet
│ │ │ └── kibana.jsonnet
│ │ │ ├── Makefile
│ │ │ ├── tests
│ │ │ ├── aks.jsonnet
│ │ │ └── gke.jsonnet
│ │ │ └── lib
│ │ │ └── utils.libsonnet
│ ├── jsonnetfile.json
│ ├── jsonnetfile.lock.json
│ └── components
│ │ ├── landingpage
│ │ ├── google.svg
│ │ ├── index.html
│ │ └── amazon.svg
│ │ ├── contour-clusterrole.json
│ │ ├── cert-manager.jsonnet
│ │ ├── base32.libsonnet
│ │ └── gangway.jsonnet
├── infrastructure
│ ├── amazon
│ │ ├── .gitignore
│ │ ├── suffix.tf
│ │ ├── secrets.tf
│ │ ├── dns.tf
│ │ ├── providers.tf
│ │ └── outputs.tf
│ ├── digitalocean
│ │ ├── .gitignore
│ │ ├── suffix.tf
│ │ ├── secrets.tf
│ │ ├── dns.tf
│ │ ├── providers.tf
│ │ └── outputs.tf
│ ├── .gitignore
│ ├── google
│ │ ├── suffix.tf
│ │ ├── secrets.tf
│ │ ├── variables.tf
│ │ ├── cluster.tf
│ │ ├── dns.tf
│ │ ├── providers.tf
│ │ └── output.tf
│ └── modules
│ │ ├── digitalocean-cluster
│ │ ├── outputs.tf
│ │ └── cluster.tf
│ │ ├── amazon-cluster
│ │ ├── outputs.tf
│ │ └── cluster.tf
│ │ ├── ca
│ │ └── ca.tf
│ │ ├── oauth2-secrets
│ │ └── secrets.tf
│ │ ├── gangway
│ │ └── secrets.tf
│ │ ├── google-dns
│ │ └── dns.tf
│ │ └── google-cluster
│ │ └── cluster.tf
├── .gitignore
└── config.dist.jsonnet
├── .gitignore
├── hack
├── boilerplate
│ ├── boilerplate.go.txt
│ ├── boilerplate.py.txt
│ ├── boilerplate.sh.txt
│ ├── boilerplate.Dockerfile.txt
│ ├── boilerplate.Makefile.txt
│ ├── test
│ │ ├── pass.go
│ │ ├── fail.go
│ │ ├── pass.py
│ │ └── fail.py
│ └── boilerplate_test.py
├── tools
│ └── tools.go
├── docker-start-wrapper.sh
├── version-ldflags.sh
├── verify-boilerplate.sh
└── update-vendor.sh
├── OWNERS
├── deploy
├── charts
│ └── kube-oidc-proxy
│ │ ├── templates
│ │ ├── serviceaccount.yaml
│ │ ├── tests
│ │ │ └── test-connection.yaml
│ │ ├── clusterrolebinding.yaml
│ │ ├── poddisruptionbudget.yaml
│ │ ├── clusterrole.yaml
│ │ ├── service.yaml
│ │ ├── ingress.yaml
│ │ ├── secret_config.yaml
│ │ ├── NOTES.txt
│ │ ├── secret_tls.yaml
│ │ └── _helpers.tpl
│ │ ├── Chart.yaml
│ │ ├── .helmignore
│ │ ├── README.md
│ │ └── values.yaml
└── yaml
│ └── secrets.yaml
├── cmd
├── app
│ └── options
│ │ ├── plugin.go
│ │ ├── audit.go
│ │ ├── serving.go
│ │ ├── client.go
│ │ ├── misc.go
│ │ ├── options.go
│ │ ├── oidc.go
│ │ └── app.go
└── main.go
├── Dockerfile
├── docs
└── tasks
│ ├── auditing.md
│ ├── no-impersonation.md
│ ├── token-passthrough.md
│ ├── extra-impersonation-headers.md
│ └── development-testing.md
├── CONTRIBUTING.md
├── GenGitChangeLog.py
├── CHANGELOG.md
├── patchlog.txt
├── CODE_OF_CONDUCT.md
└── .github
└── workflows
└── build.yaml
/test/tools/issuer/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | !/bin/kube-oidc-proxy
2 |
--------------------------------------------------------------------------------
/pkg/mocks/.gitignore:
--------------------------------------------------------------------------------
1 | /authenticator.go
2 |
--------------------------------------------------------------------------------
/test/tools/audit-webhook/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 |
--------------------------------------------------------------------------------
/test/tools/fake-apiserver/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 |
--------------------------------------------------------------------------------
/demo/manifests/.gitignore:
--------------------------------------------------------------------------------
1 | /config.json
2 | /*-config.json
3 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/VERSION:
--------------------------------------------------------------------------------
1 | dev-untagged
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /kube-oidc-proxy
2 | /bin
3 | /demo/config.jsonnet
4 | /artifacts/
5 |
--------------------------------------------------------------------------------
/demo/infrastructure/amazon/.gitignore:
--------------------------------------------------------------------------------
1 | /config-map-aws-auth_*
2 | /kubeconfig_*
3 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /secrets
2 | /.kubeconfig-*
3 | /bin/
4 | /.backup-certificates.yaml
5 |
--------------------------------------------------------------------------------
/demo/infrastructure/digitalocean/.gitignore:
--------------------------------------------------------------------------------
1 | /config-map-aws-auth_*
2 | /kubeconfig_*
3 |
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate.go.txt:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 |
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate.py.txt:
--------------------------------------------------------------------------------
1 | # Copyright Jetstack Ltd. See LICENSE for details.
2 |
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate.sh.txt:
--------------------------------------------------------------------------------
1 | # Copyright Jetstack Ltd. See LICENSE for details.
2 |
--------------------------------------------------------------------------------
/demo/infrastructure/.gitignore:
--------------------------------------------------------------------------------
1 | .terraform/
2 | *.tfstate
3 | *.tfstate.backup
4 | *.tfvars
5 |
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate.Dockerfile.txt:
--------------------------------------------------------------------------------
1 | # Copyright Jetstack Ltd. See LICENSE for details.
2 |
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate.Makefile.txt:
--------------------------------------------------------------------------------
1 | # Copyright Jetstack Ltd. See LICENSE for details.
2 |
--------------------------------------------------------------------------------
/OWNERS:
--------------------------------------------------------------------------------
1 | approvers:
2 | - simonswine
3 | - joshvanl
4 | reviewers:
5 | - simonswine
6 | - joshvanl
7 |
--------------------------------------------------------------------------------
/demo/infrastructure/amazon/suffix.tf:
--------------------------------------------------------------------------------
1 | resource "random_id" "suffix" {
2 | byte_length = 4
3 | }
4 |
--------------------------------------------------------------------------------
/demo/infrastructure/google/suffix.tf:
--------------------------------------------------------------------------------
1 | resource "random_id" "suffix" {
2 | byte_length = 4
3 | }
4 |
--------------------------------------------------------------------------------
/demo/infrastructure/digitalocean/suffix.tf:
--------------------------------------------------------------------------------
1 | resource "random_id" "suffix" {
2 | byte_length = 4
3 | }
4 |
--------------------------------------------------------------------------------
/demo/infrastructure/amazon/secrets.tf:
--------------------------------------------------------------------------------
1 | module "gangway" {
2 | source = "../modules/gangway"
3 | length = 24
4 | }
5 |
--------------------------------------------------------------------------------
/demo/infrastructure/google/secrets.tf:
--------------------------------------------------------------------------------
1 | module "gangway" {
2 | source = "../modules/gangway"
3 | length = 24
4 | }
5 |
--------------------------------------------------------------------------------
/demo/infrastructure/digitalocean/secrets.tf:
--------------------------------------------------------------------------------
1 | module "gangway" {
2 | source = "../modules/gangway"
3 | length = 24
4 | }
5 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/fluentd-es-config/system.conf:
--------------------------------------------------------------------------------
1 |
2 | root_dir /tmp/fluentd-buffers/
3 |
4 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/fluentd-es-config/fluentd.conf:
--------------------------------------------------------------------------------
1 | # Include config files in the ./config.d directory
2 | @include config.d/*.conf
3 |
--------------------------------------------------------------------------------
/demo/infrastructure/modules/digitalocean-cluster/outputs.tf:
--------------------------------------------------------------------------------
1 | output "kubeconfig" {
2 | value = "${digitalocean_kubernetes_cluster.cluster.kube_config.0.raw_config}"
3 | }
4 |
--------------------------------------------------------------------------------
/demo/infrastructure/modules/amazon-cluster/outputs.tf:
--------------------------------------------------------------------------------
1 | output "cluster_node_arn" {
2 | value = "${module.eks.worker_iam_role_arn}"
3 | }
4 |
5 | output "kubeconfig" {
6 | value = "${module.eks.kubeconfig}"
7 | }
8 |
--------------------------------------------------------------------------------
/demo/infrastructure/google/variables.tf:
--------------------------------------------------------------------------------
1 | variable "cluster_provider" {
2 | default = "google"
3 | }
4 |
5 | variable "dns_provider" {
6 | default = "google"
7 | }
8 |
9 | variable "oidc_provider" {
10 | default = "dex"
11 | }
12 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | labels:
5 | {{ include "kube-oidc-proxy.labels" . | indent 4 }}
6 | name: {{ include "kube-oidc-proxy.fullname" . }}
7 |
8 |
--------------------------------------------------------------------------------
/cmd/app/options/plugin.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | // This package is required to be imported to register all client
6 | // plugins.
7 | _ "k8s.io/client-go/plugin/pkg/client/auth"
8 | )
9 |
--------------------------------------------------------------------------------
/pkg/mocks/mocks.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package mocks
3 |
4 | // This package contains generated mocks
5 |
6 | //go:generate mockgen -package=mocks -destination authenticator.go k8s.io/apiserver/pkg/authentication/authenticator Token
7 |
--------------------------------------------------------------------------------
/demo/infrastructure/google/cluster.tf:
--------------------------------------------------------------------------------
1 | module "cluster" {
2 | source = "../modules/google-cluster"
3 | suffix = "${random_id.suffix.hex}"
4 | zone = "${var.google_zone}"
5 | }
6 |
7 | output "cluster_kubeconfig" {
8 | value = "${module.cluster.kubeconfig}"
9 | }
10 |
--------------------------------------------------------------------------------
/demo/infrastructure/google/dns.tf:
--------------------------------------------------------------------------------
1 | module "dns" {
2 | source = "../modules/google-dns"
3 | suffix = "${random_id.suffix.hex}"
4 | }
5 |
6 | module "ca" {
7 | source = "../modules/ca"
8 |
9 | ca_crt_file = "${var.ca_crt_file}"
10 | ca_key_file = "${var.ca_key_file}"
11 | }
12 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | appVersion: "v1.0.0"
3 | description: A Helm chart for kube-oidc-proxy
4 | home: https://github.com/jetstack/kube-oidc-proxy
5 | name: kube-oidc-proxy
6 | version: 0.3.2
7 | maintainers:
8 | - name: mhrabovcin
9 | - name: joshvanl
10 |
--------------------------------------------------------------------------------
/hack/tools/tools.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | //go:build tools
3 | // +build tools
4 |
5 | package tools
6 |
7 | // This file is used to vendor packages we use to build binaries.
8 | // This is the current canonical way with go modules.
9 |
10 | import (
11 | _ "github.com/golang/mock/mockgen"
12 | )
13 |
--------------------------------------------------------------------------------
/test/tools/audit-webhook/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright Jetstack Ltd. See LICENSE for details.
2 | FROM alpine:3.10
3 |
4 | LABEL description="A audit webhook sink to read audit events and write to file."
5 |
6 | RUN apk --no-cache add ca-certificates
7 |
8 | COPY ./bin/audit-webhook /usr/bin/audit-webhook
9 |
10 | CMD ["/usr/bin/audit-webhook"]
11 |
--------------------------------------------------------------------------------
/test/tools/issuer/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright Jetstack Ltd. See LICENSE for details.
2 | FROM alpine:3.10
3 |
4 | LABEL description="A basic OIDC issuer that prsents a well-known and certs endpoint."
5 |
6 | RUN apk --no-cache add ca-certificates
7 |
8 | COPY ./bin/oidc-issuer-linux /usr/bin/oidc-issuer
9 |
10 | CMD ["/usr/bin/oidc-issuer"]
11 |
--------------------------------------------------------------------------------
/test/tools/fake-apiserver/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright Jetstack Ltd. See LICENSE for details.
2 | FROM alpine:3.10
3 |
4 | LABEL description="A fake API server that will respond to requests with the same body and headers."
5 |
6 | RUN apk --no-cache add ca-certificates
7 |
8 | COPY ./bin/fake-apiserver-linux /usr/bin/fake-apiserver
9 |
10 | CMD ["/usr/bin/fake-apiserver"]
11 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright Jetstack Ltd. See LICENSE for details.
2 | FROM ubuntu:22.04
3 | LABEL description="OIDC reverse proxy authenticator based on Kubernetes"
4 |
5 | ARG TARGETARCH
6 |
7 | RUN apt-get update;apt-get -y install ca-certificates;apt-get -y upgrade;apt-get clean;rm -rf /var/lib/apt/lists/*
8 |
9 | COPY bin/${TARGETARCH}/kube-oidc-proxy /usr/bin/
10 |
11 | CMD ["/usr/bin/kube-oidc-proxy"]
12 |
--------------------------------------------------------------------------------
/demo/manifests/jsonnetfile.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": [
3 | {
4 | "name": "kube-prod-runtime",
5 | "source": {
6 | "git": {
7 | "remote": "https://github.com/bitnami/kube-prod-runtime.git",
8 | "subdir": "manifests"
9 | }
10 | },
11 | "version": "v1.1.2"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/hack/docker-start-wrapper.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -o errexit
4 | set -o nounset
5 | set -o pipefail
6 |
7 | export KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
8 |
9 | # start docker
10 | /etc/init.d/docker start
11 |
12 | # wait for it to be started
13 | n=0
14 | until [ $n -ge 15 ]
15 | do
16 | docker info > /dev/null && break
17 | n=$[$n+1]
18 | sleep 1
19 | done
20 |
21 | exec $@
22 |
--------------------------------------------------------------------------------
/demo/infrastructure/modules/ca/ca.tf:
--------------------------------------------------------------------------------
1 | variable "ca_crt_file" {}
2 | variable "ca_key_file" {}
3 |
4 | data "local_file" "crt_file" {
5 | filename = "${var.ca_crt_file}"
6 | }
7 |
8 | data "local_file" "key_file" {
9 | filename = "${var.ca_key_file}"
10 | }
11 |
12 |
13 | output "crt" {
14 | value = "${data.local_file.crt_file.content}"
15 | }
16 |
17 | output "key" {
18 | value = "${data.local_file.key_file.content}"
19 | }
20 |
--------------------------------------------------------------------------------
/demo/manifests/jsonnetfile.lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": [
3 | {
4 | "name": "kube-prod-runtime",
5 | "source": {
6 | "git": {
7 | "remote": "https://github.com/bitnami/kube-prod-runtime.git",
8 | "subdir": "manifests"
9 | }
10 | },
11 | "version": "edcd38494e95f7af2571b24d52301859719c56e5"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/pkg/util/port.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package util
3 |
4 | import (
5 | "net"
6 | "strconv"
7 | )
8 |
9 | func FreePort() (string, error) {
10 | l, err := net.ListenTCP("tcp", &net.TCPAddr{
11 | IP: net.ParseIP("127.0.0.1"),
12 | Port: 0,
13 | })
14 | if err != nil {
15 | return "", err
16 | }
17 | defer l.Close()
18 |
19 | port := l.Addr().(*net.TCPAddr).Port
20 | return strconv.Itoa(port), nil
21 | }
22 |
--------------------------------------------------------------------------------
/demo/infrastructure/amazon/dns.tf:
--------------------------------------------------------------------------------
1 | data "external" "cert_manager" {
2 | program = ["jq", ".cert_manager", "../../manifests/google-config.json"]
3 | query = { }
4 | }
5 |
6 | data "external" "externaldns" {
7 | program = ["jq", ".externaldns", "../../manifests/google-config.json"]
8 | query = { }
9 | }
10 |
11 | module "ca" {
12 | source = "../modules/ca"
13 |
14 | ca_crt_file = "${var.ca_crt_file}"
15 | ca_key_file = "${var.ca_key_file}"
16 | }
17 |
--------------------------------------------------------------------------------
/demo/infrastructure/digitalocean/dns.tf:
--------------------------------------------------------------------------------
1 | data "external" "cert_manager" {
2 | program = ["jq", ".cert_manager", "../../manifests/google-config.json"]
3 | query = {}
4 | }
5 |
6 | data "external" "externaldns" {
7 | program = ["jq", ".externaldns", "../../manifests/google-config.json"]
8 | query = {}
9 | }
10 |
11 | module "ca" {
12 | source = "../modules/ca"
13 |
14 | ca_crt_file = "${var.ca_crt_file}"
15 | ca_key_file = "${var.ca_key_file}"
16 | }
17 |
--------------------------------------------------------------------------------
/docs/tasks/auditing.md:
--------------------------------------------------------------------------------
1 | # Auditing
2 |
3 | kube-oidc-proxy allows for the ability to audit requests to the proxy. The proxy
4 | exposes all the same options for auditing that the Kubernetes API server
5 | provides, however does _not_ support dynamic configuration
6 | (`--audit-dynamic-configuration`).
7 |
8 | You can read more on how to configure and manage auditing in the [Kubernetes
9 | documentation](https://kubernetes.io/docs/tasks/debug-application-cluster/audit).
10 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/Makefile:
--------------------------------------------------------------------------------
1 | KUBECFG = kubecfg
2 |
3 | COMPONENTS := $(wildcard components/*.jsonnet)
4 | PLATFORMS := $(wildcard tests/*.jsonnet)
5 |
6 | JFILES := \
7 | $(wildcard */*.libsonnet) \
8 | $(COMPONENTS) $(PLATFORMS)
9 |
10 | validate: $(PLATFORMS:%.jsonnet=%.jsonnet-validate)
11 |
12 | %.jsonnet-validate: %.jsonnet $(JFILES)
13 | $(KUBECFG) validate --ignore-unknown $<
14 |
15 | .PHONY: all validate
16 | .PHONY: %.jsonnet-validate
17 |
--------------------------------------------------------------------------------
/demo/infrastructure/modules/oauth2-secrets/secrets.tf:
--------------------------------------------------------------------------------
1 | variable "length" {}
2 |
3 | resource "random_string" "client_id" {
4 | length = "${var.length}"
5 | special = false
6 | }
7 |
8 | resource "random_string" "client_secret" {
9 | length = "${var.length}"
10 | special = false
11 | }
12 |
13 | output "config" {
14 | value = {
15 | client_id = "${random_string.client_id.result}"
16 | client_secret = "${random_string.client_secret.result}"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *~
18 | # Various IDEs
19 | .project
20 | .idea/
21 | *.tmproj
22 | .vscode/
23 |
--------------------------------------------------------------------------------
/demo/infrastructure/google/providers.tf:
--------------------------------------------------------------------------------
1 | variable "google_region" {
2 | default = "europe-west1"
3 | }
4 |
5 | variable "google_zone" {
6 | default = "europe-west1-d"
7 | }
8 |
9 | variable "google_project" {}
10 |
11 | variable "ca_crt_file" {}
12 | variable "ca_key_file" {}
13 |
14 | provider "google" {
15 | region = "${var.google_region}"
16 | credentials = "${file("~/.config/gcloud/terraform-admin.json")}"
17 | project = "${var.google_project}"
18 | version = "~> 2.0"
19 | }
20 |
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package main
3 |
4 | import (
5 | "fmt"
6 | "os"
7 |
8 | "github.com/jetstack/kube-oidc-proxy/cmd/app"
9 | "github.com/jetstack/kube-oidc-proxy/pkg/util"
10 | "k8s.io/klog/v2"
11 | )
12 |
13 | func main() {
14 | klog.InitFlags(nil)
15 | stopCh := util.SignalHandler()
16 | cmd := app.NewRunCommand(stopCh)
17 |
18 | if err := cmd.Execute(); err != nil {
19 | fmt.Fprintf(os.Stderr, "error: %v\n", err)
20 | os.Exit(1)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/demo/infrastructure/digitalocean/providers.tf:
--------------------------------------------------------------------------------
1 | provider "digitalocean" {}
2 |
3 | variable "digitalocean_region" {
4 | default = "fra1"
5 | }
6 |
7 | variable "cluster_version" {
8 | default = "1.16.2-do.1"
9 | }
10 |
11 | module "cluster" {
12 | source = "../modules/digitalocean-cluster"
13 | suffix = "${random_id.suffix.hex}"
14 |
15 | cluster_version = "${var.cluster_version}"
16 | region = "${var.digitalocean_region}"
17 | }
18 |
19 | variable "ca_crt_file" {}
20 | variable "ca_key_file" {}
21 |
--------------------------------------------------------------------------------
/demo/infrastructure/modules/gangway/secrets.tf:
--------------------------------------------------------------------------------
1 | variable "length" {}
2 |
3 | resource "random_id" "session_security_key" {
4 | byte_length = "32"
5 | }
6 |
7 | module "oauth2" {
8 | source = "../oauth2-secrets"
9 | length = "${var.length}"
10 | }
11 |
12 | resource "random_string" "client_secret" {
13 | length = "${var.length}"
14 | special = false
15 | }
16 |
17 | output "config" {
18 | value = "${merge(module.oauth2.config,map("session_security_key",random_id.session_security_key.b64_std))}"
19 | }
20 |
--------------------------------------------------------------------------------
/demo/infrastructure/modules/digitalocean-cluster/cluster.tf:
--------------------------------------------------------------------------------
1 | variable "region" {}
2 | variable "suffix" {}
3 | variable "cluster_version" {}
4 |
5 | locals {
6 | cluster_name = "cluster-${var.suffix}"
7 | }
8 |
9 | resource "digitalocean_kubernetes_cluster" "cluster" {
10 | name = "${local.cluster_name}"
11 | version = "${var.cluster_version}"
12 | region = "${var.region}"
13 |
14 | node_pool {
15 | name = "default-pool"
16 | size = "s-2vcpu-2gb"
17 | node_count = 3
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/util/strings.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package util
3 |
4 | import "sort"
5 |
6 | func StringSlicesEqual(s1, s2 []string) bool {
7 | if len(s1) != len(s2) {
8 | return false
9 | }
10 |
11 | s12, s22 := make([]string, len(s1)), make([]string, len(s2))
12 |
13 | copy(s12, s1)
14 | copy(s22, s2)
15 |
16 | sort.Strings(s12)
17 | sort.Strings(s22)
18 |
19 | for i, s := range s12 {
20 | if s != s22[i] {
21 | return false
22 | }
23 | }
24 |
25 | return true
26 | }
27 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/tests/test-connection.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Pod
3 | metadata:
4 | name: "{{ include "kube-oidc-proxy.fullname" . }}-test-connection"
5 | labels:
6 | {{ include "kube-oidc-proxy.labels" . | indent 4 }}
7 | annotations:
8 | "helm.sh/hook": test-success
9 | spec:
10 | containers:
11 | - name: wget
12 | image: busybox
13 | command: ['wget']
14 | args: ['{{ include "kube-oidc-proxy.fullname" . }}:{{ .Values.service.port }}']
15 | restartPolicy: Never
16 |
--------------------------------------------------------------------------------
/test/e2e/framework/helper/helper.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package helper
3 |
4 | import (
5 | "k8s.io/client-go/kubernetes"
6 |
7 | "github.com/jetstack/kube-oidc-proxy/test/e2e/framework/config"
8 | )
9 |
10 | // Helper provides methods for common operations needed during tests.
11 | type Helper struct {
12 | cfg *config.Config
13 |
14 | KubeClient kubernetes.Interface
15 | }
16 |
17 | func NewHelper(cfg *config.Config) *Helper {
18 | return &Helper{
19 | cfg: cfg,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/demo/infrastructure/amazon/providers.tf:
--------------------------------------------------------------------------------
1 | variable "aws_region" {
2 | default = "eu-west-1"
3 | }
4 |
5 | variable "cloud" {
6 | default = "amazon"
7 | }
8 |
9 | variable "cluster_version" {
10 | default = "1.14"
11 | }
12 |
13 | provider "aws" {
14 | region = "${var.aws_region}"
15 | }
16 |
17 | module "cluster" {
18 | source = "../modules/amazon-cluster"
19 | suffix = "${random_id.suffix.hex}"
20 |
21 | cluster_version = "${var.cluster_version}"
22 | }
23 |
24 | variable "ca_crt_file" {}
25 | variable "ca_key_file" {}
26 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/clusterrolebinding.yaml:
--------------------------------------------------------------------------------
1 | kind: ClusterRoleBinding
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | labels:
5 | {{ include "kube-oidc-proxy.labels" . | indent 4 }}
6 | name: {{ include "kube-oidc-proxy.fullname" . }}
7 | roleRef:
8 | apiGroup: rbac.authorization.k8s.io
9 | kind: ClusterRole
10 | name: {{ include "kube-oidc-proxy.fullname" . }}
11 | subjects:
12 | - kind: ServiceAccount
13 | name: {{ include "kube-oidc-proxy.fullname" . }}
14 | namespace: {{ .Release.Namespace }}
15 |
--------------------------------------------------------------------------------
/deploy/yaml/secrets.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | data:
3 | tls.crt: {{ SERVING_TLS_CERT }}
4 | tls.key: {{ SERVING_TLS_KEY }}
5 | kind: Secret
6 | metadata:
7 | name: kube-oidc-proxy-tls
8 | namespace: kube-oidc-proxy
9 | type: kubernetes.io/tls
10 | ---
11 | apiVersion: v1
12 | data:
13 | oidc.ca-pem: {{ OIDC_CA }}
14 | oidc.issuer-url: {{ OIDC_ISSUER_URL }}
15 | oidc.username-claim: {{ OIDC_USERNAME_CLAIM }}
16 | oidc.client-id: {{ OIDC_CLIENT_ID }}
17 | kind: Secret
18 | metadata:
19 | name: kube-oidc-proxy-config
20 | namespace: kube-oidc-proxy
21 | type: Opaque
22 |
--------------------------------------------------------------------------------
/docs/tasks/no-impersonation.md:
--------------------------------------------------------------------------------
1 | # No Impersonation
2 |
3 | kube-oidc-proxy can be configured to disable impersonation. When a request has
4 | been successfully authenticated, the request is forwarded as-is, without changes
5 | to the HTTP header and no authentication injected by the proxy. The OIDC
6 | bearer token is also kept in the request. This can be useful for securing
7 | endpoints that do not provide OIDC or any authentication methods and do not
8 | implement any authorization.
9 |
10 | To disable impersonation, provide the following flag:
11 |
12 | ```
13 | --disable-impersonation
14 | ```
15 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/poddisruptionbudget.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.podDisruptionBudget.enabled -}}
2 | apiVersion: policy/v1
3 | kind: PodDisruptionBudget
4 | metadata:
5 | name: {{ include "kube-oidc-proxy.fullname" . }}
6 | namespace: {{ $.Release.Namespace }}
7 | labels:
8 | {{ include "kube-oidc-proxy.labels" . | indent 4 }}
9 | spec:
10 | minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
11 | selector:
12 | matchLabels:
13 | app.kubernetes.io/name: {{ include "kube-oidc-proxy.name" . }}
14 | app.kubernetes.io/instance: {{ .Release.Name }}
15 | {{- end }}
16 |
--------------------------------------------------------------------------------
/hack/boilerplate/test/pass.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
--------------------------------------------------------------------------------
/cmd/app/options/audit.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "github.com/spf13/pflag"
6 | apiserveroptions "k8s.io/apiserver/pkg/server/options"
7 | cliflag "k8s.io/component-base/cli/flag"
8 | )
9 |
10 | type AuditOptions struct {
11 | *apiserveroptions.AuditOptions
12 | }
13 |
14 | func NewAuditOptions(nfs *cliflag.NamedFlagSets) *AuditOptions {
15 | a := &AuditOptions{
16 | AuditOptions: apiserveroptions.NewAuditOptions(),
17 | }
18 |
19 | return a.AddFlags(nfs.FlagSet("Audit"))
20 | }
21 |
22 | func (a *AuditOptions) AddFlags(fs *pflag.FlagSet) *AuditOptions {
23 | a.AuditOptions.AddFlags(fs)
24 | return a
25 | }
26 |
--------------------------------------------------------------------------------
/hack/boilerplate/test/fail.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 The Kubernetes Authors.
3 |
4 | fail
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 | */
18 |
19 | package main
20 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/images.json:
--------------------------------------------------------------------------------
1 | {
2 | "alertmanager": "bitnami/alertmanager:0.15.3-r43",
3 | "cert-manager": "bitnami/cert-manager:0.5.2-r37",
4 | "configmap-reload": "jimmidyson/configmap-reload:v0.2.2",
5 | "elasticsearch": "bitnami/elasticsearch:5.6.15-r0",
6 | "external-dns": "bitnami/external-dns:0.5.11-r1",
7 | "fluentd": "bitnami/fluentd:1.3.3-r23",
8 | "grafana": "bitnami/grafana:5.4.3-r18",
9 | "kibana": "bitnami/kibana:5.6.15-r0",
10 | "nginx-ingress-controller": "bitnami/nginx-ingress-controller:0.21.0-r12",
11 | "oauth2_proxy": "bitnami/oauth2-proxy:3.0.0-r0",
12 | "prometheus": "bitnami/prometheus:2.6.1-r12"
13 | }
14 |
--------------------------------------------------------------------------------
/pkg/util/signals.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package util
3 |
4 | import (
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "k8s.io/klog/v2"
10 | )
11 |
12 | func SignalHandler() chan struct{} {
13 | stopCh := make(chan struct{})
14 | ch := make(chan os.Signal, 2)
15 | signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
16 |
17 | go func() {
18 | sig := <-ch
19 |
20 | close(stopCh)
21 |
22 | for i := 0; i < 3; i++ {
23 | klog.V(0).Infof("received signal %s, shutting down gracefully...", sig)
24 | sig = <-ch
25 | }
26 |
27 | klog.V(0).Infof("received signal %s, force closing", sig)
28 |
29 | os.Exit(1)
30 | }()
31 |
32 | return stopCh
33 | }
34 |
--------------------------------------------------------------------------------
/hack/boilerplate/test/pass.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2015 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | True
18 |
--------------------------------------------------------------------------------
/test/e2e/suite/cases/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package cases
3 |
4 | import (
5 | _ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/audit"
6 | _ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/headers"
7 | _ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/impersonation"
8 | _ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/passthrough"
9 | _ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/probe"
10 | _ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/rbac"
11 | _ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/token"
12 | _ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases/upgrade"
13 | )
14 |
--------------------------------------------------------------------------------
/hack/boilerplate/test/fail.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2015 The Kubernetes Authors.
4 | #
5 | # failed
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 |
--------------------------------------------------------------------------------
/demo/infrastructure/amazon/outputs.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | config = {
3 | cert_manager = "${data.external.cert_manager.result}"
4 | externaldns = "${data.external.externaldns.result}"
5 | gangway = "${module.gangway.config}"
6 |
7 | ca = {
8 | key = "${module.ca.key}"
9 | crt = "${module.ca.crt}"
10 | }
11 | }
12 | }
13 |
14 | output "config" {
15 | value = "${jsonencode(local.config)}"
16 | }
17 |
18 | output "kubeconfig_command" {
19 | value = "cp infrastructure/${var.cloud}/kubeconfig_cluster-${random_id.suffix.hex} $KUBECONFIG"
20 | }
21 |
22 | output "kubeconfig" {
23 | description = "kubectl config as generated by the module."
24 | value = "${module.cluster.kubeconfig}"
25 | }
26 |
--------------------------------------------------------------------------------
/demo/infrastructure/digitalocean/outputs.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | config = {
3 | cert_manager = "${data.external.cert_manager.result}"
4 | externaldns = "${data.external.externaldns.result}"
5 | gangway = "${module.gangway.config}"
6 |
7 | ca = {
8 | key = "${module.ca.key}"
9 | crt = "${module.ca.crt}"
10 | }
11 | }
12 | }
13 |
14 | output "config" {
15 | value = "${jsonencode(local.config)}"
16 | }
17 |
18 | output "kubeconfig_command" {
19 | value = "cd infrastructure/digitalocean/ && terraform output kubeconfig > $$KUBECONFIG"
20 | }
21 |
22 | output "kubeconfig" {
23 | description = "kubectl config as generated by the module."
24 | value = "${module.cluster.kubeconfig}"
25 | }
26 |
--------------------------------------------------------------------------------
/test/e2e/framework/config/config.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package config
3 |
4 | import (
5 | "errors"
6 |
7 | utilerrors "k8s.io/apimachinery/pkg/util/errors"
8 |
9 | "github.com/jetstack/kube-oidc-proxy/test/environment"
10 | )
11 |
12 | type Config struct {
13 | KubeConfigPath string
14 | Kubectl string
15 |
16 | RepoRoot string
17 |
18 | Environment *environment.Environment
19 | }
20 |
21 | func (c *Config) Validate() error {
22 | var errs []error
23 |
24 | if c.KubeConfigPath == "" {
25 | errs = append(errs, errors.New("kubeconfig path not defined"))
26 | }
27 |
28 | if c.RepoRoot == "" {
29 | errs = append(errs, errors.New("repo root not defined"))
30 | }
31 |
32 | return utilerrors.NewAggregate(errs)
33 | }
34 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/elasticsearch-config/java.security:
--------------------------------------------------------------------------------
1 | #
2 | # This is an alternate "security properties file".
3 | #
4 | # An alternate java.security properties file may be specified
5 | # from the command line via the system property
6 | #
7 | # -Djava.security.properties=
8 | #
9 |
10 | # The JVM defaults to caching positive hostname resolutions indefinitely.
11 | # Elasticsearch nodes rely on DNS (Kubernetes headless service), where DNS
12 | # resolutions vary with time (e.g., for node-to-node discovery). This
13 | # behaviour can be modified by adding networkaddress.cache.ttl=
14 | # to an alternate Java security properties file.
15 | # https://www.elastic.co/guide/en/elasticsearch/reference/6.3/networkaddress-cache-ttl.html
16 | networkaddress.cache.ttl=60
17 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/fluentd-es-config/output.conf:
--------------------------------------------------------------------------------
1 | # Enriches records with Kubernetes metadata
2 |
3 | @type kubernetes_metadata
4 |
5 |
6 |
7 | @id elasticsearch
8 | @type elasticsearch
9 | @log_level info
10 | type_name fluentd
11 | include_tag_key true
12 | host "#{ENV['ES_HOST']}"
13 | port 9200
14 | logstash_format true
15 |
16 | @type file
17 | path /var/log/fluentd-buffers/kubernetes.system.buffer
18 | flush_mode interval
19 | retry_type exponential_backoff
20 | flush_thread_count 2
21 | flush_interval 5s
22 | retry_forever
23 | retry_max_interval 30
24 | chunk_limit_size 2M
25 | queue_limit_length 8
26 | overflow_action block
27 |
28 |
29 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/fluentd-es-config/monitoring.conf:
--------------------------------------------------------------------------------
1 | # Prometheus Exporter Plugin
2 | # input plugin that exports metrics
3 |
4 | @type prometheus
5 |
6 |
7 |
8 | @type monitor_agent
9 |
10 |
11 | # input plugin that collects metrics from MonitorAgent
12 |
13 | @type prometheus_monitor
14 |
15 | host ${hostname}
16 |
17 |
18 |
19 | # input plugin that collects metrics for output plugin
20 |
21 | @type prometheus_output_monitor
22 |
23 | host ${hostname}
24 |
25 |
26 |
27 | # input plugin that collects metrics for in_tail plugin
28 |
29 | @type prometheus_tail_monitor
30 |
31 | host ${hostname}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/demo/infrastructure/google/output.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | config = {
3 | cert_manager = "${module.dns.config}"
4 | externaldns = "${module.dns.config}"
5 | gangway = "${module.gangway.config}"
6 |
7 | ca = {
8 | key = "${module.ca.key}"
9 | crt = "${module.ca.crt}"
10 | }
11 | }
12 | }
13 |
14 | output "config" {
15 | value = "${jsonencode(local.config)}"
16 | }
17 |
18 | # This fetches KUBECONFIG and grants full cluster admin access to the current user
19 | output "kubeconfig_command" {
20 | value = "gcloud container clusters get-credentials ${module.cluster.name} --zone ${var.google_zone} --project ${module.cluster.project} && kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value account) --dry-run -o yaml | kubectl apply -f -"
21 | }
22 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/clusterrole.yaml:
--------------------------------------------------------------------------------
1 | kind: ClusterRole
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | metadata:
4 | labels:
5 | {{ include "kube-oidc-proxy.labels" . | indent 4 }}
6 | name: {{ include "kube-oidc-proxy.fullname" . }}
7 | rules:
8 | - apiGroups:
9 | - ""
10 | resources:
11 | - "users"
12 | - "groups"
13 | - "serviceaccounts"
14 | verbs:
15 | - "impersonate"
16 | - apiGroups:
17 | - "authentication.k8s.io"
18 | resources:
19 | - "userextras/scopes"
20 | - "userextras/remote-client-ip"
21 | - "tokenreviews"
22 | # to support end user impersonation
23 | - "userextras/authentication.kubernetes.io/credential-id"
24 | - "userextras/originaluser.jetstack.io-user"
25 | - "userextras/originaluser.jetstack.io-groups"
26 | - "userextras/originaluser.jetstack.io-extra"
27 | verbs:
28 | - "create"
29 | - "impersonate"
30 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ include "kube-oidc-proxy.fullname" . }}
5 | labels:
6 | {{ include "kube-oidc-proxy.labels" . | indent 4 }}
7 | annotations:
8 | {{- range $key, $val := .Values.service.annotations }}
9 | {{ $key }}: {{ $val | quote }}
10 | {{- end }}
11 | spec:
12 | type: {{ .Values.service.type }}
13 | {{- if .Values.service.loadBalancerIP }}
14 | loadBalancerIP: "{{ .Values.service.loadBalancerIP }}"
15 | {{- end }}
16 | {{- if .Values.service.loadBalancerSourceRanges }}
17 | loadBalancerSourceRanges:
18 | {{ toYaml .Values.service.loadBalancerSourceRanges | indent 4 }}
19 | {{- end }}
20 | ports:
21 | - port: {{ .Values.service.port }}
22 | targetPort: 8443
23 | protocol: TCP
24 | name: https
25 | selector:
26 | app.kubernetes.io/name: {{ include "kube-oidc-proxy.name" . }}
27 | app.kubernetes.io/instance: {{ .Release.Name }}
28 |
--------------------------------------------------------------------------------
/demo/infrastructure/modules/google-dns/dns.tf:
--------------------------------------------------------------------------------
1 | variable "suffix" {}
2 |
3 | resource "google_service_account" "external_dns" {
4 | account_id = "external-dns-${var.suffix}"
5 | display_name = "External DNS/Cert Manager service account for GKE cluster cluster-${var.suffix}"
6 | }
7 |
8 | # https://github.com/kubernetes-incubator/external-dns/blob/v0.4.0/docs/tutorials/gke.md#set-up-your-environment
9 | resource "google_project_iam_member" "dns_admin" {
10 | role = "roles/dns.admin"
11 | member = "serviceAccount:${google_service_account.external_dns.email}"
12 | }
13 |
14 | resource "google_service_account_key" "external_dns" {
15 | service_account_id = "${google_service_account.external_dns.account_id}"
16 | }
17 |
18 | output "config" {
19 | value = {
20 | service_account_credentials = "${base64decode(google_service_account_key.external_dns.private_key)}"
21 |
22 | project = "${google_service_account.external_dns.project}"
23 | provider = "google"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/proxy/audit/handler.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package audit
3 |
4 | import (
5 | "net/http"
6 | )
7 |
8 | // This struct is used to implement an http.Handler interface. This will not
9 | // actually serve but instead implements auditing during unauthenticated
10 | // requests. It is expected that consumers of this type will call `ServeHTTP`
11 | // when an unauthenticated request is received.
12 | type unauthenticatedHandler struct {
13 | serveFunc func(http.ResponseWriter, *http.Request)
14 | }
15 |
16 | func NewUnauthenticatedHandler(a *Audit, serveFunc func(http.ResponseWriter, *http.Request)) http.Handler {
17 | u := &unauthenticatedHandler{
18 | serveFunc: serveFunc,
19 | }
20 |
21 | // if auditor is nil then return without wrapping
22 | if a == nil {
23 | return u
24 | }
25 |
26 | return a.WithUnauthorized(u)
27 | }
28 |
29 | func (u *unauthenticatedHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
30 | u.serveFunc(rw, r)
31 | }
32 |
--------------------------------------------------------------------------------
/cmd/app/options/serving.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "net"
6 |
7 | "github.com/spf13/pflag"
8 | apiserveroptions "k8s.io/apiserver/pkg/server/options"
9 | cliflag "k8s.io/component-base/cli/flag"
10 | )
11 |
12 | type SecureServingOptions struct {
13 | *apiserveroptions.SecureServingOptions
14 | }
15 |
16 | func NewSecureServingOptions(nfs *cliflag.NamedFlagSets) *SecureServingOptions {
17 | s := &SecureServingOptions{
18 | SecureServingOptions: &apiserveroptions.SecureServingOptions{
19 | BindAddress: net.ParseIP("0.0.0.0"),
20 | BindPort: 6443,
21 | Required: true,
22 | ServerCert: apiserveroptions.GeneratableKeyCert{
23 | PairName: AppName,
24 | CertDirectory: "/var/run/kubernetes",
25 | },
26 | },
27 | }
28 |
29 | return s.AddFlags(nfs.FlagSet("Secure Serving"))
30 | }
31 |
32 | func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) *SecureServingOptions {
33 | s.SecureServingOptions.AddFlags(fs)
34 | return s
35 | }
36 |
--------------------------------------------------------------------------------
/hack/version-ldflags.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Copyright 2017 The Kubernetes Authors.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | # This command is used by bazel as the workspace_status_command
17 | # to implement build stamping with git information.
18 |
19 | set -o errexit
20 | set -o nounset
21 | set -o pipefail
22 |
23 | export KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
24 |
25 | source "${KUBE_ROOT}/hack/lib/version.sh"
26 | KUBE_GO_PACKAGE=github.com/jetstack/kube-oidc-proxy
27 | kube::version::ldflags
28 |
--------------------------------------------------------------------------------
/test/e2e/framework/helper/requester.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package helper
3 |
4 | import (
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | )
9 |
10 | type Requester struct {
11 | transport http.RoundTripper
12 | token string
13 | client *http.Client
14 | }
15 |
16 | func (h *Helper) NewRequester(transport http.RoundTripper, token string) *Requester {
17 | r := &Requester{
18 | token: token,
19 | transport: transport,
20 | }
21 |
22 | r.client = http.DefaultClient
23 | r.client.Transport = r
24 |
25 | return r
26 | }
27 |
28 | func (r *Requester) RoundTrip(req *http.Request) (*http.Response, error) {
29 | req.Header.Add("Authorization", fmt.Sprintf("bearer %s", r.token))
30 | return r.transport.RoundTrip(req)
31 | }
32 |
33 | func (r *Requester) Get(target string) ([]byte, *http.Response, error) {
34 | resp, err := r.client.Get(target)
35 | if err != nil {
36 | return nil, nil, err
37 | }
38 |
39 | body, err := ioutil.ReadAll(resp.Body)
40 | if err != nil {
41 | return nil, nil, err
42 | }
43 |
44 | return body, resp, nil
45 | }
46 |
--------------------------------------------------------------------------------
/docs/tasks/token-passthrough.md:
--------------------------------------------------------------------------------
1 | # Token Passthrough
2 |
3 | kube-oidc-proxy can be configured to enable 'token passthrough' for tokens that
4 | fail OIDC authentication. If enabled, kube-oidc-proxy will perform a [token
5 | review](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication)
6 | API call to the configured target backend using the Kubernetes API. If
7 | successful, the request will be passed through as-is, with the token intact in
8 | the request and no other authentication used by kube-oidc-proxy.
9 |
10 | To enable token passthrough, include the following flag:
11 |
12 | ```
13 | --token-passthrough
14 | ```
15 |
16 | In the case of the Kubernetes API server, the authenticator, if audience aware,
17 | will validate the audiences of tokens using the audience of the API server. A
18 | new set of audiences can also be given which will be used to validate the token
19 | against. At least one of these audiences need to be present in the audiences of
20 | the token to be successful:
21 |
22 | ```
23 | ---token-passthrough-audiences=aud1.foo.bar,aud2.foo.bar
24 | ```
25 |
--------------------------------------------------------------------------------
/demo/manifests/components/landingpage/google.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/pkg/proxy/hooks/hooks.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package hooks
3 |
4 | import (
5 | "fmt"
6 | "sync"
7 |
8 | k8sErrors "k8s.io/apimachinery/pkg/util/errors"
9 | )
10 |
11 | type Hooks struct {
12 | preShutdownHooks map[string]ShutdownHook
13 | preShutdownHookLock sync.Mutex
14 | }
15 |
16 | type ShutdownHook func() error
17 |
18 | func New() *Hooks {
19 | return &Hooks{
20 | preShutdownHooks: make(map[string]ShutdownHook),
21 | }
22 | }
23 |
24 | func (h *Hooks) AddPreShutdownHook(name string, hook ShutdownHook) {
25 | h.preShutdownHookLock.Lock()
26 | defer h.preShutdownHookLock.Unlock()
27 |
28 | h.preShutdownHooks[name] = hook
29 | }
30 |
31 | // RunPreShutdownHooks runs the PreShutdownHooks for the server
32 | func (h *Hooks) RunPreShutdownHooks() error {
33 | var errs []error
34 |
35 | h.preShutdownHookLock.Lock()
36 | defer h.preShutdownHookLock.Unlock()
37 |
38 | for name, entry := range h.preShutdownHooks {
39 | if err := entry(); err != nil {
40 | errs = append(errs, fmt.Errorf("PreShutdownHook %q failed: %v", name, err))
41 | }
42 | }
43 |
44 | return k8sErrors.NewAggregate(errs)
45 | }
46 |
--------------------------------------------------------------------------------
/hack/verify-boilerplate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2014 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -o errexit
18 | set -o nounset
19 | set -o pipefail
20 |
21 | KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
22 |
23 | boilerDir="${KUBE_ROOT}/hack/boilerplate"
24 | boiler="${boilerDir}/boilerplate.py"
25 |
26 | files_need_boilerplate=($(${boiler} "$@"))
27 |
28 | # Run boilerplate check
29 | if [[ ${#files_need_boilerplate[@]} -gt 0 ]]; then
30 | for file in "${files_need_boilerplate[@]}"; do
31 | echo "Boilerplate header is wrong for: ${file}"
32 | done
33 |
34 | exit 1
35 | fi
36 |
--------------------------------------------------------------------------------
/test/tools/issuer/cmd/main.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package main
3 |
4 | import (
5 | "fmt"
6 | "os"
7 |
8 | "github.com/spf13/cobra"
9 |
10 | "github.com/jetstack/kube-oidc-proxy/pkg/util"
11 | "github.com/jetstack/kube-oidc-proxy/test/tools/issuer/cmd/options"
12 | "github.com/jetstack/kube-oidc-proxy/test/tools/issuer/pkg/issuer"
13 | )
14 |
15 | func main() {
16 | opts := new(options.Options)
17 | stopCh := util.SignalHandler()
18 |
19 | cmd := &cobra.Command{
20 | Use: "oidc-issuer",
21 | Short: "A very basic OIDC issuer to present a well-known endpoint.",
22 | RunE: func(cmd *cobra.Command, args []string) error {
23 |
24 | iss, err := issuer.New(opts.IssuerURL, opts.KeyFile, opts.CertFile, stopCh)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | compCh, err := iss.Run(opts.BindAddress, opts.ListenPort)
30 | if err != nil {
31 | return err
32 | }
33 |
34 | <-compCh
35 |
36 | return nil
37 | },
38 | }
39 |
40 | opts.AddFlags(cmd)
41 |
42 | if err := cmd.Execute(); err != nil {
43 | fmt.Fprintf(os.Stderr, "%s\n", err.Error())
44 | os.Exit(1)
45 | }
46 |
47 | os.Exit(0)
48 | }
49 |
--------------------------------------------------------------------------------
/test/tools/fake-apiserver/cmd/options/options.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | type Options struct {
9 | BindAddress string
10 | ListenPort string
11 |
12 | KeyFile string
13 | CertFile string
14 | }
15 |
16 | func (o *Options) AddFlags(cmd *cobra.Command) {
17 | cmd.PersistentFlags().StringVar(&o.BindAddress, "bind-address",
18 | "0.0.0.0", "Bound Address to listen and serve on.")
19 |
20 | cmd.PersistentFlags().StringVar(&o.ListenPort, "secure-port",
21 | "6443", "Port to serve HTTPS.")
22 | o.must(cmd.MarkPersistentFlagRequired("secure-port"))
23 |
24 | cmd.PersistentFlags().StringVar(&o.KeyFile, "tls-private-key-file",
25 | "/etc/apiserver/key.pem", "File location to key for serving.")
26 | o.must(cmd.MarkPersistentFlagRequired("tls-private-key-file"))
27 |
28 | cmd.PersistentFlags().StringVar(&o.CertFile, "tls-cert-file",
29 | "/etc/apiserver/key.pem", "File location to certificate for serving.")
30 | o.must(cmd.MarkPersistentFlagRequired("tls-cert-file"))
31 | }
32 |
33 | func (o *Options) must(err error) {
34 | if err != nil {
35 | panic(err)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/tools/audit-webhook/cmd/main.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package main
3 |
4 | import (
5 | "fmt"
6 | "os"
7 |
8 | "github.com/spf13/cobra"
9 |
10 | "github.com/jetstack/kube-oidc-proxy/pkg/util"
11 | "github.com/jetstack/kube-oidc-proxy/test/tools/audit-webhook/cmd/options"
12 | "github.com/jetstack/kube-oidc-proxy/test/tools/audit-webhook/pkg/sink"
13 | )
14 |
15 | func main() {
16 | opts := new(options.Options)
17 | stopCh := util.SignalHandler()
18 |
19 | cmd := &cobra.Command{
20 | Use: "audit-webhook",
21 | Short: "An server that implements a Kubernetes audit webhook sink",
22 | RunE: func(cmd *cobra.Command, args []string) error {
23 |
24 | wh, err := sink.New(opts.LogPath, opts.KeyFile, opts.CertFile, stopCh)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | compCh, err := wh.Run(opts.BindAddress, opts.ListenPort)
30 | if err != nil {
31 | return err
32 | }
33 |
34 | <-compCh
35 |
36 | return nil
37 | },
38 | }
39 |
40 | opts.AddFlags(cmd)
41 |
42 | if err := cmd.Execute(); err != nil {
43 | fmt.Fprintf(os.Stderr, "%s\n", err.Error())
44 | os.Exit(1)
45 | }
46 |
47 | os.Exit(0)
48 | }
49 |
--------------------------------------------------------------------------------
/test/tools/fake-apiserver/cmd/main.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package main
3 |
4 | import (
5 | "fmt"
6 | "os"
7 |
8 | "github.com/spf13/cobra"
9 |
10 | "github.com/jetstack/kube-oidc-proxy/pkg/util"
11 | "github.com/jetstack/kube-oidc-proxy/test/tools/fake-apiserver/cmd/options"
12 | "github.com/jetstack/kube-oidc-proxy/test/tools/fake-apiserver/pkg/server"
13 | )
14 |
15 | func main() {
16 | opts := new(options.Options)
17 | stopCh := util.SignalHandler()
18 |
19 | cmd := &cobra.Command{
20 | Use: "fake-apiserver",
21 | Short: "A fake apiserver that will respond to requests with the same body and headers",
22 | RunE: func(cmd *cobra.Command, args []string) error {
23 | server, err := server.New(opts.KeyFile, opts.CertFile, stopCh)
24 | if err != nil {
25 | return err
26 | }
27 |
28 | compCh, err := server.Run(opts.BindAddress, opts.ListenPort)
29 | if err != nil {
30 | return err
31 | }
32 |
33 | <-compCh
34 |
35 | return nil
36 | },
37 | }
38 |
39 | opts.AddFlags(cmd)
40 |
41 | if err := cmd.Execute(); err != nil {
42 | fmt.Fprintf(os.Stderr, "%s\n", err.Error())
43 | os.Exit(1)
44 | }
45 |
46 | os.Exit(0)
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/proxy/tokenreview/fake/tokenreview.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package fake
3 |
4 | import (
5 | "context"
6 |
7 | authv1 "k8s.io/api/authentication/v1"
8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9 | clientauthv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
10 | )
11 |
12 | var _ clientauthv1.TokenReviewInterface = &FakeReviewer{}
13 |
14 | type FakeReviewer struct {
15 | CreateFn func(*authv1.TokenReview) (*authv1.TokenReview, error)
16 | }
17 |
18 | func New() *FakeReviewer {
19 | return &FakeReviewer{
20 | CreateFn: func(*authv1.TokenReview) (*authv1.TokenReview, error) {
21 | return nil, nil
22 | },
23 | }
24 | }
25 |
26 | func (f *FakeReviewer) Create(ctx context.Context, req *authv1.TokenReview, co metav1.CreateOptions) (*authv1.TokenReview, error) {
27 | return f.CreateFn(req)
28 | }
29 |
30 | func (f *FakeReviewer) CreateContext(ctx context.Context, req *authv1.TokenReview) (*authv1.TokenReview, error) {
31 | return f.CreateFn(req)
32 | }
33 |
34 | func (f *FakeReviewer) WithCreate(req *authv1.TokenReview, err error) *FakeReviewer {
35 | f.CreateFn = func(*authv1.TokenReview) (*authv1.TokenReview, error) {
36 | return req, err
37 | }
38 |
39 | return f
40 | }
41 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/ingress.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.ingress.enabled -}}
2 | {{- $fullName := include "kube-oidc-proxy.fullname" . -}}
3 | apiVersion: networking.k8s.io/v1
4 | kind: Ingress
5 | metadata:
6 | name: {{ $fullName }}
7 | labels:
8 | {{ include "kube-oidc-proxy.labels" . | indent 4 }}
9 | {{- with .Values.ingress.annotations }}
10 | annotations:
11 | {{- toYaml . | nindent 4 }}
12 | {{- end }}
13 | spec:
14 | {{- if .Values.ingress.tls }}
15 | tls:
16 | {{- range .Values.ingress.tls }}
17 | - hosts:
18 | {{- range .hosts }}
19 | - {{ . | quote }}
20 | {{- end }}
21 | secretName: {{ .secretName }}
22 | {{- end }}
23 | {{- end }}
24 | {{- if .Values.ingress.ingressClassName }}
25 | ingressClassName: {{ .Values.ingress.ingressClassName | quote }}
26 | {{- end }}
27 | rules:
28 | {{- range .Values.ingress.hosts }}
29 | - host: {{ .host | quote }}
30 | http:
31 | paths:
32 | {{- range .paths }}
33 | - path: {{ . }}
34 | pathType: Prefix
35 | backend:
36 | service:
37 | name: {{ $fullName }}
38 | port:
39 | name: https
40 | {{- end }}
41 | {{- end }}
42 | {{- end }}
43 |
--------------------------------------------------------------------------------
/test/tools/audit-webhook/cmd/options/options.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | type Options struct {
9 | BindAddress string
10 | ListenPort string
11 |
12 | LogPath string
13 |
14 | KeyFile string
15 | CertFile string
16 | }
17 |
18 | func (o *Options) AddFlags(cmd *cobra.Command) {
19 | cmd.PersistentFlags().StringVar(&o.BindAddress, "bind-address",
20 | "0.0.0.0", "Bound Address to listen and serve on.")
21 |
22 | cmd.PersistentFlags().StringVar(&o.ListenPort, "secure-port",
23 | "6443", "Port to serve HTTPS.")
24 |
25 | cmd.PersistentFlags().StringVar(&o.LogPath, "audit-file-path",
26 | "/var/log/audit", "Filepath to write audit logs to.")
27 |
28 | cmd.PersistentFlags().StringVar(&o.KeyFile, "tls-private-key-file",
29 | "/etc/oidc/key.pem", "File location to key for serving.")
30 | o.must(cmd.MarkPersistentFlagRequired("tls-private-key-file"))
31 |
32 | cmd.PersistentFlags().StringVar(&o.CertFile, "tls-cert-file",
33 | "/etc/oidc/key.pem", "File location to certificate for serving.")
34 | o.must(cmd.MarkPersistentFlagRequired("tls-cert-file"))
35 | }
36 |
37 | func (o *Options) must(err error) {
38 | if err != nil {
39 | panic(err)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/e2e/suite/suite_test.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package suite
3 |
4 | import (
5 | "os"
6 | "path/filepath"
7 | "testing"
8 | "time"
9 |
10 | "github.com/onsi/ginkgo"
11 | ginkgoconfig "github.com/onsi/ginkgo/config"
12 | "github.com/onsi/ginkgo/reporters"
13 | "github.com/onsi/gomega"
14 | "k8s.io/apimachinery/pkg/util/wait"
15 |
16 | _ "github.com/jetstack/kube-oidc-proxy/test/e2e/suite/cases"
17 | )
18 |
19 | func init() {
20 | // Turn on verbose by default to get spec names
21 | ginkgoconfig.DefaultReporterConfig.Verbose = true
22 | // Turn on EmitSpecProgress to get spec progress (especially on interrupt)
23 | ginkgoconfig.GinkgoConfig.EmitSpecProgress = true
24 | // Randomize specs as well as suites
25 | ginkgoconfig.GinkgoConfig.RandomizeAllSpecs = true
26 |
27 | wait.ForeverTestTimeout = time.Second * 60
28 | }
29 |
30 | func TestE2E(t *testing.T) {
31 | gomega.RegisterFailHandler(ginkgo.Fail)
32 |
33 | junitPath := "../../../artifacts"
34 | if path := os.Getenv("ARTIFACTS"); path != "" {
35 | junitPath = path
36 | }
37 |
38 | junitReporter := reporters.NewJUnitReporter(filepath.Join(
39 | junitPath,
40 | "junit-go-e2e.xml",
41 | ))
42 | ginkgo.RunSpecsWithDefaultAndCustomReporters(t, "kube-oidc-proxy e2e suite", []ginkgo.Reporter{junitReporter})
43 | }
44 |
--------------------------------------------------------------------------------
/test/e2e/suite/suite.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package suite
3 |
4 | import (
5 | "path/filepath"
6 |
7 | . "github.com/onsi/ginkgo"
8 | log "github.com/sirupsen/logrus"
9 |
10 | "github.com/jetstack/kube-oidc-proxy/test/e2e/framework"
11 | "github.com/jetstack/kube-oidc-proxy/test/environment"
12 | )
13 |
14 | var (
15 | env *environment.Environment
16 | cfg = framework.DefaultConfig
17 | )
18 |
19 | var _ = SynchronizedBeforeSuite(func() []byte {
20 | var err error
21 | env, err = environment.New(1, 0)
22 | if err != nil {
23 | log.Fatalf("Error provisioning environment: %s", err)
24 | }
25 |
26 | if err := env.Create(); err != nil {
27 | log.Fatalf("Error creating environment: %s", err)
28 | }
29 |
30 | cfg.KubeConfigPath = env.KubeConfigPath()
31 | cfg.Kubectl = filepath.Join(env.RootPath(), "bin", "kubectl")
32 | cfg.RepoRoot = env.RootPath()
33 | cfg.Environment = env
34 |
35 | if err := framework.DefaultConfig.Validate(); err != nil {
36 | log.Fatalf("Invalid test config: %s", err)
37 | }
38 |
39 | return nil
40 | }, func([]byte) {
41 | })
42 |
43 | var _ = SynchronizedAfterSuite(func() {},
44 | func() {
45 | if env != nil {
46 | if err := env.Destory(); err != nil {
47 | log.Fatalf("Failed to destory environment: %s", err)
48 | }
49 | }
50 | },
51 | )
52 |
--------------------------------------------------------------------------------
/test/e2e/framework/helper/kubectl.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package helper
3 |
4 | import (
5 | "io"
6 | "os"
7 | "os/exec"
8 | "strings"
9 | )
10 |
11 | type Kubectl struct {
12 | namespace string
13 | kubectl string
14 | kubeconfig string
15 | }
16 |
17 | func (k *Kubectl) Describe(resources ...string) error {
18 | resourceNames := strings.Join(resources, ",")
19 | return k.Run("describe", resourceNames)
20 | }
21 |
22 | func (k *Kubectl) DescribeResource(resource, name string) error {
23 | return k.Run("describe", resource, name)
24 | }
25 |
26 | func (h *Helper) Kubectl(ns string) *Kubectl {
27 | return &Kubectl{
28 | namespace: ns,
29 | kubectl: h.cfg.Kubectl,
30 | kubeconfig: h.cfg.KubeConfigPath,
31 | }
32 | }
33 |
34 | func (k *Kubectl) Run(args ...string) error {
35 | return k.RunWithStdout(os.Stdout, args...)
36 | }
37 |
38 | func (k *Kubectl) RunWithStdout(stdout io.Writer, args ...string) error {
39 | baseArgs := []string{"--kubeconfig", k.kubeconfig}
40 | if k.namespace == "" {
41 | baseArgs = append(baseArgs, "--all-namespaces")
42 | } else {
43 | baseArgs = []string{"--namespace", k.namespace}
44 | }
45 | args = append(baseArgs, args...)
46 | cmd := exec.Command(k.kubectl, args...)
47 | cmd.Stdout = stdout
48 | cmd.Stderr = os.Stderr
49 | return cmd.Run()
50 | }
51 |
--------------------------------------------------------------------------------
/test/tools/issuer/cmd/options/options.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "github.com/spf13/cobra"
6 | )
7 |
8 | type Options struct {
9 | BindAddress string
10 | ListenPort string
11 |
12 | IssuerURL string
13 |
14 | KeyFile string
15 | CertFile string
16 | }
17 |
18 | func (o *Options) AddFlags(cmd *cobra.Command) {
19 | cmd.PersistentFlags().StringVar(&o.BindAddress, "bind-address",
20 | "0.0.0.0", "Bound Address to listen and serve on.")
21 |
22 | cmd.PersistentFlags().StringVar(&o.ListenPort, "secure-port",
23 | "6443", "Port to serve HTTPS.")
24 | o.must(cmd.MarkPersistentFlagRequired("secure-port"))
25 |
26 | cmd.PersistentFlags().StringVar(&o.IssuerURL, "issuer-url",
27 | "", "URL of the issuer that appears in well-known responses.")
28 | o.must(cmd.MarkPersistentFlagRequired("issuer-url"))
29 |
30 | cmd.PersistentFlags().StringVar(&o.KeyFile, "tls-private-key-file",
31 | "/etc/oidc/key.pem", "File location to key for serving.")
32 | o.must(cmd.MarkPersistentFlagRequired("tls-private-key-file"))
33 |
34 | cmd.PersistentFlags().StringVar(&o.CertFile, "tls-cert-file",
35 | "/etc/oidc/key.pem", "File location to certificate for serving.")
36 | o.must(cmd.MarkPersistentFlagRequired("tls-cert-file"))
37 | }
38 |
39 | func (o *Options) must(err error) {
40 | if err != nil {
41 | panic(err)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/docs/tasks/extra-impersonation-headers.md:
--------------------------------------------------------------------------------
1 | # Extra Impersonation Headers
2 |
3 | kube-oidc-proxy has support for adding 'extra' headers to the impersonation user
4 | info. This can be useful for passing extra information onto the target server
5 | about the proxy or client. kube-oidc-proxy currently supports two configuration
6 | options.
7 |
8 | # Client IP
9 |
10 | The following flag can be passed which will append the remote client IP as an
11 | extra header:
12 |
13 | `--extra-user-header-client-ip`
14 |
15 | Proxied requests will then contain the header
16 | `Impersonate-Extra-Remote-Client-Ip: ` where `` is
17 | the address of the source connection of the request. Note that this IP address
18 | may not be the real client IP address when the request is being proxied. If the
19 | `X-Forwarded-For` header is present in the request then the value will be used
20 | as the client address.
21 |
22 | # Extra User Headers
23 |
24 | The following flag accepts a number of key value pairs that will be added as
25 | extra impersonation headers with proxied requests. This flag accepts a number of
26 | key value pairs, separated by commas, where a single key may have multiple
27 | values:
28 |
29 | `--extra-user-headers=key1=foo,key2=bar,key1=bar`
30 |
31 | Proxied requests will then contain the headers
32 |
33 | `Impersonate-Extra-Key1: foo,bar`
34 | `Impersonate-Extra-Key2: foo`
35 |
--------------------------------------------------------------------------------
/demo/config.dist.jsonnet:
--------------------------------------------------------------------------------
1 | local main = import './manifests/main.jsonnet';
2 |
3 | function(cloud='google') main {
4 | cloud: cloud,
5 | // this will only run the google cluster
6 | clouds: {
7 | google: main.clouds.google,
8 | amazon: main.clouds.amazon,
9 | digitalocean: main.clouds.digitalocean,
10 | },
11 | base_domain: '.kubernetes.example.net',
12 | cert_manager+: {
13 | letsencrypt_contact_email:: 'certificates@example.net',
14 | solvers+: [
15 | //{
16 | // http01: {
17 | // ingress: {},
18 | // },
19 | //},
20 | {
21 | dns01: {
22 | clouddns: {
23 | project: $.config.cert_manager.project,
24 | serviceAccountSecretRef: {
25 | name: $.cert_manager.google_secret.metadata.name,
26 | key: 'credentials.json',
27 | },
28 | },
29 | },
30 | },
31 | ],
32 | },
33 | dex+: if $.master then {
34 | users: [
35 | $.dex.Password('admin@example.net', '$2y$10$i2.tSLkchjnpvnI73iSW/OPAVriV9BWbdfM6qemBM1buNRu81.ZG.'), // plaintext: secure
36 | ],
37 | // This shows how to add dex connectors
38 | connectors: [
39 | $.dex.Connector('github', 'GitHub', 'github', {
40 | clientID: '0123',
41 | clientSecret: '4567',
42 | orgs: [{
43 | name: 'example-net',
44 | }],
45 | }),
46 | ],
47 | } else {
48 | },
49 | }
50 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/secret_config.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Secret
3 | metadata:
4 | name: {{ include "kube-oidc-proxy.fullname" . }}-config
5 | labels:
6 | {{ include "kube-oidc-proxy.labels" . | indent 4 }}
7 | type: Opaque
8 | data:
9 | {{- if .Values.oidc.caPEM }}
10 | oidc.ca-pem: {{ .Values.oidc.caPEM | default "" | b64enc }}
11 | {{- end }}
12 |
13 | {{- if .Values.oidc.issuerUrl }}
14 | oidc.issuer-url: {{ .Values.oidc.issuerUrl | b64enc }}
15 | {{- end }}
16 |
17 | {{- if .Values.oidc.usernameClaim }}
18 | oidc.username-claim: {{ .Values.oidc.usernameClaim | default "" | b64enc }}
19 | {{- end }}
20 |
21 | {{- if .Values.oidc.clientId }}
22 | oidc.client-id: {{ .Values.oidc.clientId | b64enc }}
23 | {{- end }}
24 |
25 | {{- if .Values.oidc.usernamePrefix }}
26 | oidc.username-prefix: {{ .Values.oidc.usernamePrefix | default "" | b64enc }}
27 | {{- end }}
28 |
29 | {{- if .Values.oidc.groupsClaim }}
30 | oidc.groups-claim: {{ .Values.oidc.groupsClaim | default "" | b64enc }}
31 | {{- end }}
32 |
33 | {{- if .Values.oidc.groupsPrefix }}
34 | oidc.groups-prefix: {{ .Values.oidc.groupsPrefix | default "" | b64enc }}
35 | {{- end }}
36 |
37 | {{- if .Values.oidc.signingAlgs }}
38 | oidc.signing-algs: {{ join "," .Values.oidc.signingAlgs | default "" | b64enc }}
39 | {{- end }}
40 |
41 | {{- if .Values.oidc.requiredClaims }}
42 | oidc.required-claims: {{ include "requiredClaims" . | b64enc }}
43 | {{- end }}
44 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/tests/aks.jsonnet:
--------------------------------------------------------------------------------
1 | /*
2 | * Bitnami Kubernetes Production Runtime - A collection of services that makes it
3 | * easy to run production workloads in Kubernetes.
4 | *
5 | * Copyright 2018-2019 Bitnami
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | // Test AKS
21 |
22 | (import "../platforms/aks.jsonnet") {
23 | "letsencrypt_contact_email": "noone@nowhere.com",
24 | config: {
25 | dnsZone: "test.example.com",
26 | externalDns: {
27 | tenantId: "mytenant",
28 | subscriptionId: "mysubscription",
29 | aadClientId: "myclientid",
30 | aadClientSecret: "mysecret",
31 | resourceGroup: "test-resource-group",
32 | },
33 | oauthProxy: {
34 | client_id: "myclientid",
35 | client_secret: "mysecret",
36 | cookie_secret: "cookiesecret",
37 | authz_domain: "test.invalid",
38 | azure_tenant: "mytenant",
39 | },
40 | },
41 | }
42 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/tests/gke.jsonnet:
--------------------------------------------------------------------------------
1 | /*
2 | * Bitnami Kubernetes Production Runtime - A collection of services that makes it
3 | * easy to run production workloads in Kubernetes.
4 | *
5 | * Copyright 2018-2019 Bitnami
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | // Test GKE
21 |
22 | (import "../platforms/gke.jsonnet") {
23 | "letsencrypt_contact_email": "noone@nowhere.com",
24 | config: {
25 | dnsZone: "test.example.com",
26 | externalDns: {
27 | credentials: "google credentials json contents",
28 | project: "dns_gcp_project",
29 | },
30 | oauthProxy: {
31 | client_id: "myclientid",
32 | client_secret: "mysecret",
33 | cookie_secret: "cookiesecret",
34 | authz_domain: "test.invalid",
35 | google_groups: [],
36 | google_admin_email: "admin@example.com",
37 | google_service_account_json: "",
38 | },
39 | },
40 | }
41 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/fluentd-es-config/import-from-upstream.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Bitnami Kubernetes Production Runtime - A collection of services that makes it
4 | # easy to run production workloads in Kubernetes.
5 | #
6 | # Copyright 2018-2019 Bitnami
7 | #
8 | # Licensed under the Apache License, Version 2.0 (the "License");
9 | # you may not use this file except in compliance with the License.
10 | # You may obtain a copy of the License at
11 | #
12 | # http://www.apache.org/licenses/LICENSE-2.0
13 | #
14 | # Unless required by applicable law or agreed to in writing, software
15 | # distributed under the License is distributed on an "AS IS" BASIS,
16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | # See the License for the specific language governing permissions and
18 | # limitations under the License.
19 |
20 | # Save *.conf files needed for fluentd-es log pre-processing from upstream
21 | # yaml-s
22 |
23 | import os
24 | import yaml
25 | import requests
26 |
27 | r = requests.get("https://raw.githubusercontent.com/kubernetes/kubernetes/"
28 | "master/cluster/addons/fluentd-elasticsearch/"
29 | "fluentd-es-configmap.yaml")
30 | DIR = os.path.dirname(__file__)
31 |
32 | for fname, content in yaml.safe_load(r.text)['data'].items():
33 | fname = os.path.join(DIR, fname)
34 | print("Saving to " + fname)
35 | with open(fname, "w") as f:
36 | f.write(content)
37 | f.write("\n") # Add trailing newline
38 |
--------------------------------------------------------------------------------
/test/e2e/suite/cases/probe/probe.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package probe
3 |
4 | import (
5 | "time"
6 |
7 | . "github.com/onsi/ginkgo"
8 | . "github.com/onsi/gomega"
9 |
10 | "github.com/jetstack/kube-oidc-proxy/test/e2e/framework"
11 | "github.com/jetstack/kube-oidc-proxy/test/kind"
12 | )
13 |
14 | var _ = framework.CasesDescribe("Readiness Probe", func() {
15 | f := framework.NewDefaultFramework("readiness-probe")
16 |
17 | It("Should not become ready if the issuer is unavailable", func() {
18 | By("Deleting the Issuer so no longer becomes reachable")
19 | Expect(f.Helper().DeleteIssuer(f.Namespace.Name)).NotTo(HaveOccurred())
20 |
21 | By("Deleting the current proxy that is ready")
22 | Expect(f.Helper().DeleteProxy(f.Namespace.Name)).NotTo(HaveOccurred())
23 |
24 | By("Deploying the Proxy should never become ready as the issuer is unavailable")
25 | _, _, err := f.Helper().DeployProxy(f.Namespace, f.IssuerURL(),
26 | f.ClientID(), f.IssuerKeyBundle(), nil)
27 | // Error should occur (not ready)
28 | Expect(err).To(HaveOccurred())
29 | })
30 |
31 | It("Should continue to be ready even if the issuer becomes unavailable", func() {
32 | By("Deleting the Issuer so no longer becomes reachable")
33 | Expect(f.Helper().DeleteIssuer(f.Namespace.Name)).NotTo(HaveOccurred())
34 |
35 | time.Sleep(time.Second * 10)
36 |
37 | err := f.Helper().WaitForDeploymentReady(f.Namespace.Name, kind.ProxyImageName, time.Second*5)
38 | Expect(err).NotTo(HaveOccurred())
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/demo/manifests/components/contour-clusterrole.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "rbac.authorization.k8s.io/v1beta1",
3 | "kind": "ClusterRole",
4 | "metadata": {
5 | "name": "contour"
6 | },
7 | "rules": [
8 | {
9 | "apiGroups": [
10 | ""
11 | ],
12 | "resources": [
13 | "configmaps",
14 | "endpoints",
15 | "nodes",
16 | "pods",
17 | "secrets"
18 | ],
19 | "verbs": [
20 | "list",
21 | "watch"
22 | ]
23 | },
24 | {
25 | "apiGroups": [
26 | ""
27 | ],
28 | "resources": [
29 | "nodes"
30 | ],
31 | "verbs": [
32 | "get"
33 | ]
34 | },
35 | {
36 | "apiGroups": [
37 | ""
38 | ],
39 | "resources": [
40 | "services"
41 | ],
42 | "verbs": [
43 | "get",
44 | "list",
45 | "watch"
46 | ]
47 | },
48 | {
49 | "apiGroups": [
50 | "extensions"
51 | ],
52 | "resources": [
53 | "ingresses"
54 | ],
55 | "verbs": [
56 | "get",
57 | "list",
58 | "watch"
59 | ]
60 | },
61 | {
62 | "apiGroups": [
63 | "contour.heptio.com"
64 | ],
65 | "resources": [
66 | "ingressroutes",
67 | "tlscertificatedelegations"
68 | ],
69 | "verbs": [
70 | "get",
71 | "list",
72 | "watch",
73 | "put",
74 | "post",
75 | "patch"
76 | ]
77 | }
78 | ]
79 | }
80 |
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # Copyright 2016 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | import boilerplate
18 | import unittest
19 | import StringIO
20 | import os
21 | import sys
22 |
23 | class TestBoilerplate(unittest.TestCase):
24 | """
25 | Note: run this test from the hack/boilerplate directory.
26 |
27 | $ python -m unittest boilerplate_test
28 | """
29 |
30 | def test_boilerplate(self):
31 | os.chdir("test/")
32 |
33 | class Args(object):
34 | def __init__(self):
35 | self.filenames = []
36 | self.rootdir = "."
37 | self.boilerplate_dir = "../"
38 | self.verbose = True
39 |
40 | # capture stdout
41 | old_stdout = sys.stdout
42 | sys.stdout = StringIO.StringIO()
43 |
44 | boilerplate.args = Args()
45 | ret = boilerplate.main()
46 |
47 | output = sorted(sys.stdout.getvalue().split())
48 |
49 | sys.stdout = old_stdout
50 |
51 | self.assertEquals(
52 | output, ['././fail.go', '././fail.py'])
53 |
--------------------------------------------------------------------------------
/pkg/util/token.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package util
3 |
4 | import (
5 | "net/http"
6 | "strings"
7 | "time"
8 |
9 | "gopkg.in/square/go-jose.v2"
10 | "gopkg.in/square/go-jose.v2/jwt"
11 | )
12 |
13 | // Return just the token from the header of the request, without 'bearer'.
14 | func ParseTokenFromRequest(req *http.Request) (string, bool) {
15 | if req == nil || req.Header == nil {
16 | return "", false
17 | }
18 |
19 | auth := strings.TrimSpace(req.Header.Get("Authorization"))
20 | if auth == "" {
21 | return "", false
22 | }
23 |
24 | parts := strings.Split(auth, " ")
25 | if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
26 | return "", false
27 | }
28 |
29 | token := parts[1]
30 |
31 | // Empty bearer tokens aren't valid
32 | if len(token) == 0 {
33 | return "", false
34 | }
35 |
36 | return token, true
37 | }
38 |
39 | // fakeJWT generates a valid JWT using the passed input parameters which is
40 | // signed by a generated key. This is useful for checking the status of a
41 | // signer.
42 | func FakeJWT(issuerURL string) (string, error) {
43 | key := []byte("secret")
44 |
45 | sig, err := jose.NewSigner(
46 | jose.SigningKey{Algorithm: jose.HS256, Key: key},
47 | (&jose.SignerOptions{}).WithType("JWT"))
48 | if err != nil {
49 | return "", err
50 | }
51 |
52 | cl := jwt.Claims{
53 | Subject: "fake",
54 | Issuer: issuerURL,
55 | NotBefore: jwt.NewNumericDate(time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)),
56 | Audience: jwt.Audience(nil),
57 | }
58 |
59 | return jwt.Signed(sig).Claims(cl).CompactSerialize()
60 | }
61 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/NOTES.txt:
--------------------------------------------------------------------------------
1 | 1. Get the application URL by running these commands:
2 | {{- if .Values.ingress.enabled }}
3 | {{- range $host := .Values.ingress.hosts }}
4 | {{- range .paths }}
5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
6 | {{- end }}
7 | {{- end }}
8 | {{- else if contains "NodePort" .Values.service.type }}
9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "kube-oidc-proxy.fullname" . }})
10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
11 | echo http://$NODE_IP:$NODE_PORT
12 | {{- else if contains "LoadBalancer" .Values.service.type }}
13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available.
14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "kube-oidc-proxy.fullname" . }}'
15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "kube-oidc-proxy.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
16 | echo http://$SERVICE_IP:{{ .Values.service.port }}
17 | {{- else if contains "ClusterIP" .Values.service.type }}
18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "kube-oidc-proxy.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
19 | echo "Visit http://127.0.0.1:8080 to use your application"
20 | kubectl port-forward $POD_NAME 8080:80
21 | {{- end }}
22 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/secret_tls.yaml:
--------------------------------------------------------------------------------
1 | {{ $fullname := include "kube-oidc-proxy.fullname" . }}
2 | {{ $ca := genCA (printf "%s-ca" $fullname) 3650 }}
3 | {{ $cn := printf "%s.%s.svc.cluster.local" $fullname .Release.Namespace }}
4 | {{ $in := printf "%s-issuer" $fullname }}
5 |
6 | {{ if .Values.tls.certManager }}
7 | {{ if .Values.tls.selfSigned }}
8 | apiVersion: cert-manager.io/v1
9 | kind: Issuer
10 | metadata:
11 | name: {{ template "kube-oidc-proxy.fullname" . }}-issuer
12 | spec:
13 | selfSigned: {}
14 | ---
15 | {{ end }}
16 | apiVersion: cert-manager.io/v1
17 | kind: Certificate
18 | metadata:
19 | name: {{ template "kube-oidc-proxy.fullname" . }}-tls
20 | spec:
21 | commonName: {{ template "kube-oidc-proxy.fullname" . }}-tls
22 | dnsNames:
23 | - {{ $cn }}
24 | secretName: {{ template "kube-oidc-proxy.fullname" . }}-tls
25 | issuerRef:
26 | group: cert-manager.io
27 | kind: Issuer
28 | name: {{ .Values.tls.issuerName | default $in }}
29 | {{ if .Values.tls.selfSigned }}
30 | duration: 3650h0m0s
31 | privateKey:
32 | algorithm: RSA
33 | encoding: PKCS8
34 | size: 2048
35 | renewBefore: 24h0m0s
36 | usages:
37 | - server auth
38 | {{ end }}
39 | {{ else }}
40 | {{- if (not .Values.tls.secretName) }}
41 | {{ $server := genSignedCert $cn nil nil 365 $ca }}
42 | apiVersion: v1
43 | kind: Secret
44 | type: kubernetes.io/tls
45 | metadata:
46 | name: {{ template "kube-oidc-proxy.fullname" . }}-tls
47 | labels:
48 | {{ include "kube-oidc-proxy.labels" . | indent 4 }}
49 | data:
50 | tls.crt: {{ b64enc $server.Cert }}
51 | tls.key: {{ b64enc $server.Key }}
52 | {{ end }}
53 | {{ end }}
--------------------------------------------------------------------------------
/pkg/probe/probe.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package probe
3 |
4 | import (
5 | "context"
6 | "fmt"
7 | "net"
8 | "net/http"
9 | "strings"
10 | "time"
11 |
12 | "github.com/heptiolabs/healthcheck"
13 | "k8s.io/apiserver/pkg/authentication/authenticator"
14 | "k8s.io/klog/v2"
15 | )
16 |
17 | const (
18 | timeout = time.Second * 10
19 | )
20 |
21 | type HealthCheck struct {
22 | handler healthcheck.Handler
23 |
24 | oidcAuther authenticator.Token
25 | fakeJWT string
26 |
27 | ready bool
28 | }
29 |
30 | func Run(port, fakeJWT string, oidcAuther authenticator.Token) error {
31 | h := &HealthCheck{
32 | handler: healthcheck.NewHandler(),
33 | oidcAuther: oidcAuther,
34 | fakeJWT: fakeJWT,
35 | }
36 |
37 | h.handler.AddReadinessCheck("secure serving", h.Check)
38 |
39 | go func() {
40 | for {
41 | err := http.ListenAndServe(net.JoinHostPort("0.0.0.0", port), h.handler)
42 | if err != nil {
43 | klog.Errorf("ready probe listener failed: %s", err)
44 | }
45 | time.Sleep(5 * time.Second)
46 | }
47 | }()
48 |
49 | return nil
50 | }
51 |
52 | func (h *HealthCheck) Check() error {
53 | if h.ready {
54 | return nil
55 | }
56 |
57 | ctx, cancel := context.WithTimeout(context.Background(), timeout)
58 | defer cancel()
59 |
60 | _, _, err := h.oidcAuther.AuthenticateToken(ctx, h.fakeJWT)
61 | if err != nil && strings.HasSuffix(err.Error(), "authenticator not initialized") {
62 | err = fmt.Errorf("OIDC provider not yet initialized: %s", err)
63 | klog.V(4).Infof(err.Error())
64 | return err
65 | }
66 |
67 | h.ready = true
68 |
69 | klog.V(4).Info("OIDC provider initialized.")
70 |
71 | return nil
72 | }
73 |
--------------------------------------------------------------------------------
/test/util/tls.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package util
3 |
4 | import (
5 | "crypto/rsa"
6 | "crypto/x509"
7 | "encoding/pem"
8 | "fmt"
9 | "io/ioutil"
10 | "net"
11 | "os"
12 | "path/filepath"
13 |
14 | "k8s.io/client-go/util/cert"
15 | )
16 |
17 | type KeyBundle struct {
18 | CertPath string
19 | KeyPath string
20 | CertBytes []byte
21 | KeyBytes []byte
22 | Key *rsa.PrivateKey
23 | }
24 |
25 | const (
26 | prefix = "kube-oidc-proxy"
27 | )
28 |
29 | func NewTLSSelfSignedCertKey(host string, netIPs []net.IP, dnsNames []string) (*KeyBundle, error) {
30 | certBytes, keyBytes, err := cert.GenerateSelfSignedCertKey(host, netIPs, dnsNames)
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | dir, err := ioutil.TempDir(os.TempDir(), prefix)
36 | if err != nil {
37 | return nil, err
38 | }
39 | defer os.RemoveAll(dir)
40 |
41 | certPath := filepath.Join(dir, fmt.Sprintf("%s-ca.pem", prefix))
42 | keyPath := filepath.Join(dir, fmt.Sprintf("%s-key.pem", prefix))
43 |
44 | err = ioutil.WriteFile(certPath, certBytes, 0600)
45 | if err != nil {
46 | return nil, err
47 | }
48 |
49 | err = ioutil.WriteFile(keyPath, keyBytes, 0600)
50 | if err != nil {
51 | return nil, err
52 | }
53 |
54 | p, rest := pem.Decode(keyBytes)
55 | if len(rest) != 0 {
56 | return nil, fmt.Errorf("got rest decoding pem file %s: %s", keyPath, rest)
57 | }
58 |
59 | sk, err := x509.ParsePKCS1PrivateKey(p.Bytes)
60 | if err != nil {
61 | return nil, err
62 | }
63 |
64 | return &KeyBundle{
65 | CertPath: certPath,
66 | KeyPath: keyPath,
67 | CertBytes: certBytes,
68 | KeyBytes: keyBytes,
69 | Key: sk,
70 | }, nil
71 | }
72 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/alertmanager-config.jsonnet:
--------------------------------------------------------------------------------
1 | /*
2 | * Bitnami Kubernetes Production Runtime - A collection of services that makes it
3 | * easy to run production workloads in Kubernetes.
4 | *
5 | * Copyright 2018-2019 Bitnami
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | // https://prometheus.io/docs/alerting/configuration/
21 | {
22 | global: {
23 | resolve_timeout: "5m",
24 | },
25 |
26 | //templates: []
27 |
28 | route: {
29 | group_by: ["alertname", "cluster", "service"],
30 |
31 | group_wait: "30s",
32 |
33 | group_interval: "5m",
34 | repeat_interval: "7d",
35 |
36 | receiver: "email",
37 |
38 | routes: [
39 | ],
40 | },
41 |
42 | inhibit_rules: [
43 | {
44 | source_match: {severity: "critical"},
45 | target_match: {severity: "warning"},
46 | equal: ["alertname", "cluster", "service"],
47 | },
48 | ],
49 |
50 | receivers_:: {
51 | email: {
52 | //email_configs: [{to: "foo@example.com"}],
53 | },
54 | },
55 | receivers: [{name: k} + self.receivers_[k] for k in std.objectFields(self.receivers_)],
56 | }
57 |
--------------------------------------------------------------------------------
/demo/infrastructure/modules/google-cluster/cluster.tf:
--------------------------------------------------------------------------------
1 | variable "suffix" {}
2 |
3 | variable "zone" {}
4 |
5 | resource "google_container_cluster" "cluster" {
6 | name = "cluster-${var.suffix}"
7 | zone = "${var.zone}"
8 |
9 | initial_node_count = 3
10 |
11 | # Setting an empty username and password explicitly disables basic auth
12 | master_auth {
13 | username = ""
14 | password = ""
15 | }
16 |
17 | node_config {
18 | oauth_scopes = [
19 | "https://www.googleapis.com/auth/compute",
20 | "https://www.googleapis.com/auth/devstorage.read_only",
21 | "https://www.googleapis.com/auth/logging.write",
22 | "https://www.googleapis.com/auth/monitoring",
23 | ]
24 | }
25 | }
26 |
27 | data "google_client_config" "default" {}
28 |
29 | output "name" {
30 | value = "${google_container_cluster.cluster.name}"
31 | }
32 |
33 | output "project" {
34 | value = "${google_container_cluster.cluster.project}"
35 | }
36 |
37 | output "kubeconfig" {
38 | value = < 0 {
22 | sec, err = h.KubeClient.CoreV1().Secrets(ns).Get(context.TODO(), sa.Secrets[0].Name, metav1.GetOptions{})
23 | if err != nil {
24 | return nil, err
25 | }
26 | } else {
27 | var requestSeconds int64
28 | requestSeconds = 600
29 |
30 | // starting in 1.24 ServiceAccounts no longer get Secrets, need to request one bound to a ServiceAccount
31 | serviceAccountToken, err := h.KubeClient.CoreV1().ServiceAccounts(ns).CreateToken(context.TODO(), sa.Name, &v1.TokenRequest{
32 | Spec: v1.TokenRequestSpec{
33 | Audiences: []string{"https://kubernetes.default.svc.cluster.local"},
34 | ExpirationSeconds: &requestSeconds,
35 | },
36 | }, metav1.CreateOptions{})
37 |
38 | if err != nil {
39 | return nil, err
40 | }
41 |
42 | fmt.Printf("TOKEN!!!!! : %v\n", serviceAccountToken.Status.Token)
43 |
44 | secretToReturn := &corev1.Secret{
45 | ObjectMeta: metav1.ObjectMeta{
46 | Namespace: ns,
47 | Name: name,
48 | },
49 | Data: map[string][]byte{corev1.ServiceAccountTokenKey: []byte(serviceAccountToken.Status.Token)},
50 | }
51 |
52 | return secretToReturn, nil
53 |
54 | }
55 |
56 | return sec, nil
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/proxy/logging/accesslog_test.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package logging
3 |
4 | import (
5 | "net/http"
6 | "testing"
7 | )
8 |
9 | func TestXForwardedFor(t *testing.T) {
10 |
11 | tests := map[string]struct {
12 | headers http.Header
13 | remoteAddr string
14 | exp string
15 | }{
16 | "no x-forwarded-for": {
17 | headers: http.Header{},
18 | remoteAddr: "1.2.3.4",
19 | exp: "",
20 | },
21 | "empty x-forwarded-for": {
22 | headers: http.Header{
23 | "X-Forwarded-For": []string{""},
24 | },
25 | remoteAddr: "1.2.3.4",
26 | exp: "",
27 | },
28 | "x-forwarded-for is remoteaddr": {
29 | headers: http.Header{
30 | "X-Forwarded-For": []string{"1.2.3.4"},
31 | },
32 | remoteAddr: "1.2.3.4",
33 | exp: "",
34 | },
35 | "x-forwarded-for with no remoteaddr": {
36 | headers: http.Header{
37 | "X-Forwarded-For": []string{"1.2.3.1"},
38 | },
39 | remoteAddr: "1.2.3.4",
40 | exp: "1.2.3.1",
41 | },
42 | "x-forwarded-for with with remoteaddr at the end": {
43 | headers: http.Header{
44 | "X-Forwarded-For": []string{"1.2.3.1, 1.2.3.4"},
45 | },
46 | remoteAddr: "1.2.3.4",
47 | exp: "1.2.3.1",
48 | },
49 | "x-forwarded-for with with remoteaddr at the beginning": {
50 | headers: http.Header{
51 | "X-Forwarded-For": []string{"1.2.3.4, 1.2.3.1"},
52 | },
53 | remoteAddr: "1.2.3.4",
54 | exp: "1.2.3.1",
55 | },
56 | }
57 |
58 | for name, test := range tests {
59 | t.Run(name, func(t *testing.T) {
60 | forwarded := findXForwardedFor(test.headers, test.remoteAddr)
61 |
62 | if test.exp != forwarded {
63 | t.Errorf("failed for %s: unexpected result : %s", name, forwarded)
64 | t.FailNow()
65 | }
66 | })
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/test/e2e/framework/util.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package framework
3 |
4 | import (
5 | "context"
6 | "fmt"
7 | "time"
8 |
9 | corev1 "k8s.io/api/core/v1"
10 | apierrors "k8s.io/apimachinery/pkg/api/errors"
11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12 | "k8s.io/apimachinery/pkg/util/wait"
13 | "k8s.io/client-go/kubernetes"
14 | )
15 |
16 | const (
17 | clientID = "kube-oidc-proxy-e2e-client_id"
18 | )
19 |
20 | // CreateKubeNamespace creates a new Kubernetes Namespace for a test.
21 | func (f *Framework) CreateKubeNamespace(baseName string) (*corev1.Namespace, error) {
22 | ns := &corev1.Namespace{
23 | ObjectMeta: metav1.ObjectMeta{
24 | GenerateName: fmt.Sprintf("kube-oidc-proxy-e2e-%v-", baseName),
25 | },
26 | }
27 |
28 | return f.KubeClientSet.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{})
29 | }
30 |
31 | // DeleteKubeNamespace will delete a namespace resource
32 | func (f *Framework) DeleteKubeNamespace(namespace string) error {
33 | return f.KubeClientSet.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{})
34 | }
35 |
36 | // WaitForKubeNamespaceNotExist will wait for the namespace with the given name
37 | // to not exist for up to 2 minutes.
38 | func (f *Framework) WaitForKubeNamespaceNotExist(namespace string) error {
39 | return wait.PollImmediate(time.Second*2, time.Minute*2, namespaceNotExist(f.KubeClientSet, namespace))
40 | }
41 |
42 | func namespaceNotExist(c kubernetes.Interface, namespace string) wait.ConditionFunc {
43 | return func() (bool, error) {
44 | _, err := c.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{})
45 | if apierrors.IsNotFound(err) {
46 | return true, nil
47 | }
48 | if err != nil {
49 | return false, err
50 | }
51 | return false, nil
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/cmd/app/options/client.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "github.com/spf13/cobra"
6 | "github.com/spf13/pflag"
7 | "k8s.io/cli-runtime/pkg/genericclioptions"
8 | cliflag "k8s.io/component-base/cli/flag"
9 | )
10 |
11 | type ClientOptions struct {
12 | *genericclioptions.ConfigFlags
13 |
14 | KubeClientQPS float32
15 | KubeClientBurst int
16 | }
17 |
18 | func NewClientOptions(nfs *cliflag.NamedFlagSets) *ClientOptions {
19 | c := &ClientOptions{
20 | ConfigFlags: genericclioptions.NewConfigFlags(true),
21 | }
22 |
23 | // Disable unwanted options
24 | c.CacheDir = nil
25 | c.Impersonate = nil
26 | c.ImpersonateGroup = nil
27 |
28 | return c.AddFlags(nfs.FlagSet("Client"))
29 | }
30 |
31 | func (c *ClientOptions) AddFlags(fs *pflag.FlagSet) *ClientOptions {
32 | c.ConfigFlags.AddFlags(fs)
33 |
34 | // Extra flags
35 | fs.Float32Var(&c.KubeClientQPS, "kube-client-qps", c.KubeClientQPS, "Sets the QPS on the app "+
36 | "kubernetes client, this will configure throttling on requests sent to the apiserver "+
37 | "(If not set, it will use client default ones)")
38 | fs.IntVar(&c.KubeClientBurst, "kube-client-burst", c.KubeClientBurst, "Sets the burst on the app "+
39 | "kubernetes client, this will configure throttling on requests sent to the apiserver"+
40 | "(If not set, it will use client default ones)")
41 |
42 | return c
43 | }
44 |
45 | func (c *ClientOptions) ClientFlagsChanged(cmd *cobra.Command) bool {
46 | for _, f := range clientOptionFlags() {
47 | if ff := cmd.Flag(f); ff != nil && ff.Changed {
48 | return true
49 | }
50 | }
51 |
52 | return false
53 | }
54 |
55 | func clientOptionFlags() []string {
56 | return []string{"certificate-authority", "client-certificate", "client-key", "cluster",
57 | "context", "insecure-skip-tls-verify", "kubeconfig", "namespace",
58 | "request-timeout", "server", "token", "user",
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/proxy/subjectaccessreview/fake/subjectaccessreview.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package fake
3 |
4 | import (
5 | "context"
6 |
7 | azv1 "k8s.io/api/authorization/v1"
8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9 | clientazv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
10 | )
11 |
12 | var _ clientazv1.SubjectAccessReviewInterface = &FakeReviewer{}
13 |
14 | type FakeReviewer struct {
15 | err error
16 | }
17 |
18 | func New(err error) *FakeReviewer {
19 | return &FakeReviewer{
20 | err: err,
21 | }
22 | }
23 |
24 | func (f *FakeReviewer) Create(ctx context.Context, req *azv1.SubjectAccessReview, co metav1.CreateOptions) (*azv1.SubjectAccessReview, error) {
25 | if f.err != nil {
26 | return nil, f.err
27 | }
28 |
29 | if req.Spec.ResourceAttributes.Resource == "users" && req.Spec.ResourceAttributes.Name == "jjackson" {
30 | req.Status = azv1.SubjectAccessReviewStatus{
31 | Allowed: true,
32 | }
33 |
34 | return req, nil
35 | }
36 |
37 | if req.Spec.ResourceAttributes.Resource == "groups" && req.Spec.ResourceAttributes.Name == "group3" {
38 | req.Status = azv1.SubjectAccessReviewStatus{
39 | Allowed: true,
40 | }
41 |
42 | return req, nil
43 | }
44 |
45 | if req.Spec.ResourceAttributes.Resource == "uids" && req.Spec.ResourceAttributes.Name == "1-2-3-4" {
46 | req.Status = azv1.SubjectAccessReviewStatus{
47 | Allowed: true,
48 | }
49 |
50 | return req, nil
51 | }
52 |
53 | if req.Spec.ResourceAttributes.Resource == "userextras" && req.Spec.ResourceAttributes.Subresource == "remoteaddr" && req.Spec.ResourceAttributes.Name == "1.2.3.4" {
54 | req.Status = azv1.SubjectAccessReviewStatus{
55 | Allowed: true,
56 | }
57 |
58 | return req, nil
59 | }
60 |
61 | // not an expcted test, or didn't conform to known allowed, fail
62 | req.Status = azv1.SubjectAccessReviewStatus{
63 | Allowed: false,
64 | }
65 |
66 | return req, nil
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/proxy/tokenreview/tokenreview.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package tokenreview
3 |
4 | import (
5 | "context"
6 | "errors"
7 | "fmt"
8 | "net/http"
9 | "time"
10 |
11 | authv1 "k8s.io/api/authentication/v1"
12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13 | "k8s.io/client-go/kubernetes"
14 | clientauthv1 "k8s.io/client-go/kubernetes/typed/authentication/v1"
15 | "k8s.io/client-go/rest"
16 |
17 | "github.com/jetstack/kube-oidc-proxy/pkg/util"
18 | )
19 |
20 | var (
21 | timeout = time.Second * 10
22 | )
23 |
24 | type TokenReview struct {
25 | reviewRequester clientauthv1.TokenReviewInterface
26 | audiences []string
27 | }
28 |
29 | func New(restConfig *rest.Config, audiences []string) (*TokenReview, error) {
30 | kubeclient, err := kubernetes.NewForConfig(restConfig)
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | return &TokenReview{
36 | reviewRequester: kubeclient.AuthenticationV1().TokenReviews(),
37 | audiences: audiences,
38 | }, nil
39 | }
40 |
41 | func (t *TokenReview) Review(req *http.Request) (bool, error) {
42 | token, ok := util.ParseTokenFromRequest(req)
43 | if !ok {
44 | return false, errors.New("bearer token not found in request")
45 | }
46 |
47 | review := t.buildReview(token)
48 |
49 | ctx, cancel := context.WithTimeout(req.Context(), timeout)
50 | defer cancel()
51 |
52 | resp, err := t.reviewRequester.Create(ctx, review, metav1.CreateOptions{})
53 | if err != nil {
54 | return false, err
55 | }
56 |
57 | if len(resp.Status.Error) > 0 {
58 | return false, fmt.Errorf("error authenticating using token review: %s",
59 | resp.Status.Error)
60 | }
61 |
62 | return resp.Status.Authenticated, nil
63 | }
64 |
65 | func (t *TokenReview) buildReview(token string) *authv1.TokenReview {
66 | return &authv1.TokenReview{
67 | Spec: authv1.TokenReviewSpec{
68 | Token: token,
69 | Audiences: t.audiences,
70 | },
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/* vim: set filetype=mustache: */}}
2 | {{/*
3 | Expand the name of the chart.
4 | */}}
5 | {{- define "kube-oidc-proxy.name" -}}
6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
7 | {{- end -}}
8 |
9 | {{/*
10 | Create a default fully qualified app name.
11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
12 | If release name contains chart name it will be used as a full name.
13 | */}}
14 | {{- define "kube-oidc-proxy.fullname" -}}
15 | {{- if .Values.fullnameOverride -}}
16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
17 | {{- else -}}
18 | {{- $name := default .Chart.Name .Values.nameOverride -}}
19 | {{- if contains $name .Release.Name -}}
20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}}
21 | {{- else -}}
22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
23 | {{- end -}}
24 | {{- end -}}
25 | {{- end -}}
26 |
27 | {{/*
28 | Create chart name and version as used by the chart label.
29 | */}}
30 | {{- define "kube-oidc-proxy.chart" -}}
31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
32 | {{- end -}}
33 |
34 | {{/*
35 | Common labels
36 | */}}
37 | {{- define "kube-oidc-proxy.labels" -}}
38 | app.kubernetes.io/name: {{ include "kube-oidc-proxy.name" . }}
39 | helm.sh/chart: {{ include "kube-oidc-proxy.chart" . }}
40 | app.kubernetes.io/instance: {{ .Release.Name }}
41 | {{- if .Chart.AppVersion }}
42 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
43 | {{- end }}
44 | app.kubernetes.io/managed-by: {{ .Release.Service }}
45 | {{- end -}}
46 |
47 | {{/*
48 | Required claims serialized to CLI argument
49 | */}}
50 | {{- define "requiredClaims" -}}
51 | {{- if .Values.oidc.requiredClaims -}}
52 | {{- $local := (list) -}}
53 | {{- range $k, $v := .Values.oidc.requiredClaims -}}
54 | {{- $local = (printf "%s=%s" $k $v | append $local) -}}
55 | {{- end -}}
56 | {{ join "," $local }}
57 | {{- end -}}
58 | {{- end -}}
59 |
--------------------------------------------------------------------------------
/demo/manifests/components/cert-manager.jsonnet:
--------------------------------------------------------------------------------
1 | local kube = import '../vendor/kube-prod-runtime/lib/kube.libsonnet';
2 | local cert_manager_manifests = import './cert-manager/cert-manager.json';
3 | local apiGroup = 'cert-manager.io/v1alpha2';
4 |
5 | {
6 | ca_secret_name:: 'ca-key-pair',
7 |
8 | p:: '',
9 | metadata:: {
10 | metadata+: {
11 | namespace: 'kubeprod',
12 | },
13 | },
14 | letsencrypt_contact_email:: error 'Letsencrypt contact e-mail is undefined',
15 |
16 | // create simple to use certificate resource
17 | Certificate(namespace, name, issuer, solver, domains):: kube._Object(apiGroup, 'Certificate', name) + {
18 | metadata+: {
19 | namespace: namespace,
20 | name: name,
21 | },
22 | spec+: {
23 | secretName: name + '-tls',
24 | dnsNames: domains,
25 | issuerRef: {
26 | name: issuer.metadata.name,
27 | kind: issuer.kind,
28 | },
29 | },
30 | },
31 |
32 | // Letsencrypt environments
33 | letsencrypt_environments:: {
34 | prod: $.letsencryptProd.metadata.name,
35 | staging: $.letsencryptStaging.metadata.name,
36 | },
37 | // Letsencrypt environment (defaults to the production one)
38 | letsencrypt_environment:: 'prod',
39 |
40 | Issuer(name):: kube._Object(apiGroup, 'Issuer', name) {
41 | },
42 |
43 | ClusterIssuer(name):: kube._Object(apiGroup, 'ClusterIssuer', name) {
44 | },
45 |
46 | deploy: cert_manager_manifests,
47 |
48 | letsencryptStaging: $.ClusterIssuer($.p + 'letsencrypt-staging') {
49 | local this = self,
50 | spec+: {
51 | acme+: {
52 | server: 'https://acme-staging-v02.api.letsencrypt.org/directory',
53 | email: $.letsencrypt_contact_email,
54 | privateKeySecretRef: { name: this.metadata.name },
55 | http01: {},
56 | },
57 | },
58 | },
59 |
60 | letsencryptProd: $.letsencryptStaging {
61 | metadata+: { name: $.p + 'letsencrypt-prod' },
62 | spec+: {
63 | acme+: {
64 | server: 'https://acme-v02.api.letsencrypt.org/directory',
65 | },
66 | },
67 | },
68 |
69 | solvers+:: [],
70 | }
71 |
--------------------------------------------------------------------------------
/docs/tasks/development-testing.md:
--------------------------------------------------------------------------------
1 | # Development Testing
2 |
3 | In order to help development for the proxy, there are a few tools in place for
4 | quick testing.
5 |
6 | # Creating a Cluster
7 |
8 | Use `make dev_cluster_create` to spin up a kind cluster locally. This will also
9 | build the proxy and other tooling from source, build their images, and load them
10 | onto each node.
11 |
12 | # Deploying the Proxy
13 |
14 | This will build the proxy and other tooling from source,build the images, and
15 | load them onto each node. This will then deploy the proxy alongside a fake OIDC
16 | issuer so that the proxy is fully functional. The proxy will then be reachable
17 | from a node port service in the cluster.
18 |
19 |
20 | ```bash
21 | make dev_cluster_deploy
22 | ```
23 |
24 | This command will output a signed OIDC token that is valid for the proxy. You
25 | can then make calls to the proxy, like the following:
26 |
27 | ```bash
28 | curl -k https://172.17.0.2:30226 -H 'Authorization: bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.ewoJImlzcyI6Imh0dHBzOi8vb2lkYy1pc3N1ZXItZTJlLmt1YmUtb2lkYy1wcm94eS1lMmUtNmhiNGcuc3ZjLmNsdXN0ZXIubG9jYWw6NjQ0MyIsCgkiYXVkIjpbImt1YmUtb2lkYy1wcm94eS1lMmUtY2xpZW50LWlkIiwiYXVkLTIiXSwKCSJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLAoJImdyb3VwcyI6WyJncm91cC0xIiwiZ3JvdXAtMiJdLAoJImV4cCI6MTU4MjU1NTYzMQoJfQ.qWCM5zUHGslmwbgyZnMjhVeCLJd3R3c7xjtatjT_pv1VY-PpJ8IGBsbcCpur1fAm2CAbr0juM3yzwV1S3TUjhNhE8Wo6rxjA2Flnmwj7Nn2Got6T_cMFHQ_3A6YC72qkMwH-7SvXFB-C5Bk96vi9-clrxJ_b1XjfMPViZEVCJphh9HVzrZ5DPOAR0PDl-qnVys_CRkF0NEwEvAZL5SFumBqjtLBI9XUlWbB6VTljPOExL1zkv8NevZF8DxVsYFaW9HOYH8vNgC07kj_oUVkmAjP-2tVngcBKka0IBmuz2r-RfWNy9VJ-yb19AbtJNw6fjASy7O6VifuH4ZpjP5JSIg'
29 | ```
30 |
31 | You are also able to deploy a server that the proxy connects to. This is useful
32 | for checking the headers and request body sent to the target server by the
33 | proxy which are present in the server logs. To enable this, set the following
34 | environment variable:
35 |
36 | ```bash
37 | KUBE_OIDC_PROXY_FAKE_APISERVER=true make dev_cluster_deploy
38 | ```
39 |
40 | # Delete the cluster
41 |
42 | To delete the test kind cluster, use `make dev_cluster_destroy`.
43 |
--------------------------------------------------------------------------------
/pkg/util/flags/string_to_string_slice_test.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package flags
3 |
4 | import (
5 | "reflect"
6 | "testing"
7 | )
8 |
9 | func TestStringToStringSliceSet(t *testing.T) {
10 | tests := map[string]struct {
11 | val string
12 | expError bool
13 | expValues map[string][]string
14 | }{
15 | "if empty string set, no values": {
16 | val: "",
17 | expError: false,
18 | expValues: make(map[string][]string),
19 | },
20 | "if only key then error": {
21 | val: "key1",
22 | expError: true,
23 | expValues: make(map[string][]string),
24 | },
25 | "if single key value return": {
26 | val: "key1=foo",
27 | expError: false,
28 | expValues: map[string][]string{
29 | "key1": []string{"foo"},
30 | },
31 | },
32 | "if two keys with two values return": {
33 | val: "key1=foo,key2=bar",
34 | expError: false,
35 | expValues: map[string][]string{
36 | "key1": []string{"foo"},
37 | "key2": []string{"bar"},
38 | },
39 | },
40 | "if 3 keys with 5 values return": {
41 | val: "key1=foo,key2=bar,key1=a,key2=c,key3=c",
42 | expError: false,
43 | expValues: map[string][]string{
44 | "key1": []string{"foo", "a"},
45 | "key2": []string{"bar", "c"},
46 | "key3": []string{"c"},
47 | },
48 | },
49 | "if key with no value error": {
50 | val: "key1=foo,key2=bar,key1=a,key2=c,key3",
51 | expError: true,
52 | expValues: make(map[string][]string),
53 | },
54 | }
55 |
56 | for name, test := range tests {
57 | t.Run(name, func(t *testing.T) {
58 | s := new(stringToStringSliceValue)
59 |
60 | err := s.Set(test.val)
61 | if test.expError != (err != nil) {
62 | t.Errorf("got unexpected error: %v", err)
63 | t.FailNow()
64 | }
65 |
66 | match := true
67 | if s.values == nil {
68 | if test.expValues != nil {
69 | match = false
70 | }
71 | } else if !reflect.DeepEqual(test.expValues, *s.values) {
72 | match = false
73 | }
74 |
75 | if !match {
76 | t.Errorf("unexpected values, exp=%v got=%v", test.expValues, *s.values)
77 | }
78 | })
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/hack/update-vendor.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Copyright 2019 The Kubernetes Authors.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | set -o errexit
17 | set -o nounset
18 | set -o pipefail
19 |
20 | REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
21 |
22 | # Usage:
23 | # hack/pin-dependency.sh $MODULE $SHA-OR-TAG
24 | #
25 | # Example:
26 | # hack/pin-dependency.sh github.com/docker/docker 501cb131a7b7
27 |
28 | # Explicitly opt into go modules, even though we're inside a GOPATH directory
29 | export GO111MODULE=on
30 | # Explicitly clear GOPATH, to ensure nothing this script calls makes use of that path info
31 | export GOPATH=
32 | # Explicitly clear GOFLAGS, since GOFLAGS=-mod=vendor breaks dependency resolution while rebuilding vendor
33 | export GOFLAGS=
34 | # Detect problematic GOPROXY settings that prevent lookup of dependencies
35 | if [[ "${GOPROXY:-}" == "off" ]]; then
36 | echo "Cannot run hack/pin-dependency.sh with \$GOPROXY=off"
37 | exit 1
38 | fi
39 |
40 | dep="${1:-}"
41 | sha="${2:-}"
42 | if [[ -z "${dep}" || -z "${sha}" ]]; then
43 | echo "Usage:"
44 | echo " hack/pin-dependency.sh \$MODULE \$SHA-OR-TAG"
45 | echo ""
46 | echo "Example:"
47 | echo " hack/pin-dependency.sh github.com/docker/docker 501cb131a7b7"
48 | echo ""
49 | exit 1
50 | fi
51 |
52 | # Add the require directive
53 | echo "Running: go get ${dep}@${sha}"
54 | go get -d "${dep}@${sha}"
55 |
56 | # Find the resolved version
57 | rev=$(go mod edit -json | jq -r ".Require[] | select(.Path == \"${dep}\") | .Version")
58 | echo "Resolved to ${dep}@${rev}"
59 |
60 | # Add the replace directive
61 | echo "Running: go mod edit -replace ${dep}=${dep}@${rev}"
62 | go mod edit -replace "${dep}=${dep}@${rev}"
63 |
--------------------------------------------------------------------------------
/pkg/util/flags/string_to_string_slice.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package flags
3 |
4 | import (
5 | "bytes"
6 | "encoding/csv"
7 | "fmt"
8 | "strings"
9 |
10 | "github.com/spf13/pflag"
11 | )
12 |
13 | // This is a struct to implement the pflag.Value interface to introduce a
14 | // map[string][]string flag type.
15 | type stringToStringSliceValue struct {
16 | values *map[string][]string
17 | }
18 |
19 | var _ pflag.Value = &stringToStringSliceValue{}
20 |
21 | // NewStringToStringSliceValue returns a pflag.Value interface that implements
22 | // a flag that takes values into the map[string][]slice data structure.
23 | func NewStringToStringSliceValue(p *map[string][]string) pflag.Value {
24 | return &stringToStringSliceValue{
25 | values: p,
26 | }
27 | }
28 |
29 | // This format is expecting a list of key value pairs, seperated by commas. A
30 | // single index may have multiple entries.
31 | // e.g.: a=-7,b=2,a=3
32 | func (s *stringToStringSliceValue) Set(val string) error {
33 | if s.values == nil {
34 | m := make(map[string][]string)
35 | s.values = &m
36 | }
37 |
38 | if *s.values == nil {
39 | *s.values = make(map[string][]string)
40 | }
41 |
42 | if len(val) == 0 {
43 | return nil
44 | }
45 |
46 | var ss []string
47 |
48 | r := csv.NewReader(strings.NewReader(val))
49 | var err error
50 | ss, err = r.Read()
51 | if err != nil {
52 | *s.values = make(map[string][]string)
53 | return err
54 | }
55 |
56 | for _, pair := range ss {
57 | kv := strings.Split(pair, "=")
58 | if len(kv) != 2 {
59 | *s.values = make(map[string][]string)
60 | return fmt.Errorf("%s must be formatted as key=value", pair)
61 | }
62 |
63 | (*s.values)[kv[0]] = append((*s.values)[kv[0]], kv[1])
64 | }
65 |
66 | return nil
67 | }
68 |
69 | func (s *stringToStringSliceValue) Type() string {
70 | return "stringToStringSlice"
71 | }
72 |
73 | func (s *stringToStringSliceValue) String() string {
74 | var records []string
75 | for k, vs := range *s.values {
76 | for _, v := range vs {
77 | records = append(records, k+"="+v)
78 | }
79 | }
80 |
81 | var buf bytes.Buffer
82 | w := csv.NewWriter(&buf)
83 | if err := w.Write(records); err != nil {
84 | panic(err)
85 | }
86 |
87 | w.Flush()
88 | return "[" + strings.TrimSpace(buf.String()) + "]"
89 | }
90 |
--------------------------------------------------------------------------------
/test/e2e/framework/helper/token.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package helper
3 |
4 | import (
5 | "crypto/tls"
6 | "crypto/x509"
7 | "fmt"
8 | "net/http"
9 | "net/url"
10 | "time"
11 |
12 | jose "gopkg.in/square/go-jose.v2"
13 | "k8s.io/client-go/rest"
14 |
15 | "github.com/jetstack/kube-oidc-proxy/test/util"
16 | )
17 |
18 | func (h *Helper) NewValidRestConfig(issuerBundle, proxyBundle *util.KeyBundle,
19 | issuerURL, proxyURL *url.URL, clientID string) (*rest.Config, error) {
20 |
21 | // Valid token with exp in 10 minutes
22 | tokenPayload := h.NewTokenPayload(issuerURL, clientID,
23 | time.Now().Add(time.Minute*10))
24 | signedToken, err := h.SignToken(issuerBundle, tokenPayload)
25 | if err != nil {
26 | return nil, fmt.Errorf("failed to sign token %q: %s", tokenPayload, err)
27 | }
28 |
29 | certPool := x509.NewCertPool()
30 | if ok := certPool.AppendCertsFromPEM(proxyBundle.CertBytes); !ok {
31 | return nil, fmt.Errorf("failed to append proxy cert data to cert pool %s", proxyBundle.CertBytes)
32 | }
33 |
34 | return &rest.Config{
35 | Host: proxyURL.String(),
36 | Burst: rest.DefaultBurst,
37 | BearerToken: signedToken,
38 | Transport: &http.Transport{
39 | TLSClientConfig: &tls.Config{
40 | RootCAs: certPool,
41 | },
42 | },
43 | }, nil
44 | }
45 |
46 | func (h *Helper) SignToken(issuerBundle *util.KeyBundle, tokenPayload []byte) (string, error) {
47 | signer, err := jose.NewSigner(jose.SigningKey{
48 | Algorithm: jose.SignatureAlgorithm("RS256"),
49 | Key: issuerBundle.Key,
50 | }, nil)
51 | if err != nil {
52 | return "", fmt.Errorf("failed to initialise new jwt signer: %s", err)
53 | }
54 |
55 | jwt, err := signer.Sign(tokenPayload)
56 | if err != nil {
57 | return "", fmt.Errorf("failed to sign jwt: %s", err)
58 | }
59 |
60 | signedToken, err := jwt.CompactSerialize()
61 | if err != nil {
62 | return "", err
63 | }
64 |
65 | return signedToken, nil
66 | }
67 |
68 | func (h *Helper) NewTokenPayload(issuerURL *url.URL, clientID string, exp time.Time) []byte {
69 | // Valid for 10 mins
70 | return []byte(fmt.Sprintf(`{
71 | "iss":"%s",
72 | "aud":["%s","aud-2"],
73 | "email":"user@example.com",
74 | "groups":["group-1","group-2"],
75 | "exp":%d
76 | }`, issuerURL, clientID, exp.Unix()))
77 | }
78 |
--------------------------------------------------------------------------------
/demo/manifests/components/landingpage/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Kube-OIDC-Proxy
7 |
8 |
9 |
10 |
11 |
29 |
30 |
31 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | #CONTENT#
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/cmd/app/options/misc.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "fmt"
6 | "os"
7 | "runtime"
8 |
9 | "github.com/spf13/pflag"
10 | apimachineryversion "k8s.io/apimachinery/pkg/version"
11 | cliflag "k8s.io/component-base/cli/flag"
12 | "k8s.io/component-base/cli/globalflag"
13 | )
14 |
15 | type MiscOptions struct {
16 | gitMajor string // major version, always numeric
17 | gitMinor string // minor version, numeric possibly followed by "+"
18 | gitVersion string
19 | gitCommit string // sha1 from git, output of $(git rev-parse HEAD)
20 | gitTreeState string // state of git tree, either "clean" or "dirty"
21 |
22 | buildDate string // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
23 | }
24 |
25 | var (
26 | gitMajor string // major version, always numeric
27 | gitMinor string // minor version, numeric possibly followed by "+"
28 | gitVersion = "v0.0.0-master+$Format:%h$"
29 | gitCommit = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD)
30 | gitTreeState = "" // state of git tree, either "clean" or "dirty"
31 |
32 | buildDate = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
33 | )
34 |
35 | func NewMiscOptions(nfs *cliflag.NamedFlagSets) *MiscOptions {
36 | m := &MiscOptions{
37 | gitMajor: gitMajor,
38 | gitMinor: gitMinor,
39 | gitVersion: gitVersion,
40 | gitCommit: gitCommit,
41 | gitTreeState: gitTreeState,
42 | buildDate: buildDate,
43 | }
44 |
45 | return m.AddFlags(nfs.FlagSet("Misc"))
46 | }
47 |
48 | func (m *MiscOptions) AddFlags(fs *pflag.FlagSet) *MiscOptions {
49 | globalflag.AddGlobalFlags(fs, AppName)
50 | fs.Bool("version", false, "Print version information and quit")
51 | return m
52 | }
53 |
54 | func (m *MiscOptions) PrintVersionAndExit() {
55 | fmt.Printf("%s version: %#v\n", AppName,
56 | apimachineryversion.Info{
57 | Major: m.gitMajor,
58 | Minor: m.gitMinor,
59 | GitVersion: m.gitVersion,
60 | GitCommit: m.gitCommit,
61 | GitTreeState: m.gitTreeState,
62 | BuildDate: m.buildDate,
63 | GoVersion: runtime.Version(),
64 | Compiler: runtime.Compiler,
65 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
66 | },
67 | )
68 |
69 | os.Exit(0)
70 | }
71 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/version.jsonnet:
--------------------------------------------------------------------------------
1 | /*
2 | * Bitnami Kubernetes Production Runtime - A collection of services that makes it
3 | * easy to run production workloads in Kubernetes.
4 | *
5 | * Copyright 2018-2019 Bitnami
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | local kube = import "../lib/kube.libsonnet";
21 |
22 | local trim = function(str) (
23 | if std.startsWith(str, " ") || std.startsWith(str, "\n") then
24 | trim(std.substr(str, 1, std.length(str) - 1))
25 | else if std.endsWith(str, " ") || std.endsWith(str, "\n") then
26 | trim(std.substr(str, 0, std.length(str) - 1))
27 | else
28 | str
29 | );
30 |
31 | local VERSION = trim(importstr "../VERSION");
32 |
33 | {
34 | p:: "",
35 | metadata:: {
36 | metadata+: {
37 | namespace: "kubeprod",
38 | },
39 | },
40 |
41 | // This is intended as a publicly available place to see the details
42 | // of the BKPR install. If you ever want to know which version of
43 | // BKPR is currently installed, this is the place you should look.
44 | config: kube.ConfigMap("release") + $.metadata {
45 | data+: {
46 | release: VERSION,
47 | // There may be additional fields here in future
48 | },
49 | },
50 |
51 | readerRole: kube.Role($.p + "release-reader") + $.metadata {
52 | rules: [
53 | {
54 | apiGroups: [""],
55 | resources: ["configmap"],
56 | resourceNames: [$.config.metadata.name],
57 | verbs: ["get", "list", "watch"],
58 | },
59 | ],
60 | },
61 |
62 | readerRoleBinding: kube.RoleBinding($.p + "release-read-public") + $.metadata {
63 | roleRef_: $.readerRole,
64 | subjects: [{
65 | kind: "Group",
66 | name: "system:authenticated",
67 | apiGroup: "rbac.authorization.k8s.io",
68 | }],
69 | },
70 | }
71 |
--------------------------------------------------------------------------------
/test/tools/fake-apiserver/pkg/server/server.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package server
3 |
4 | import (
5 | "encoding/pem"
6 | "fmt"
7 | "io/ioutil"
8 | "net"
9 | "net/http"
10 |
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | type Server struct {
15 | keyFile, certFile string
16 |
17 | stopCh <-chan struct{}
18 | }
19 |
20 | func New(keyFile, certFile string, stopCh <-chan struct{}) (*Server, error) {
21 | b, err := ioutil.ReadFile(keyFile)
22 | if err != nil {
23 | return nil, err
24 | }
25 |
26 | block, _ := pem.Decode(b)
27 | if block == nil {
28 | return nil,
29 | fmt.Errorf("failed to parse PEM block containing the key: %q", keyFile)
30 | }
31 |
32 | return &Server{
33 | keyFile: keyFile,
34 | certFile: certFile,
35 | stopCh: stopCh,
36 | }, nil
37 | }
38 |
39 | func (s *Server) Run(bindAddress, listenPort string) (<-chan struct{}, error) {
40 | serveAddr := fmt.Sprintf("%s:%s", bindAddress, listenPort)
41 |
42 | l, err := net.Listen("tcp", serveAddr)
43 | if err != nil {
44 | return nil, err
45 | }
46 |
47 | go func() {
48 | <-s.stopCh
49 | if l != nil {
50 | l.Close()
51 | }
52 | }()
53 |
54 | compCh := make(chan struct{})
55 | go func() {
56 | defer close(compCh)
57 |
58 | err := http.ServeTLS(l, s, s.certFile, s.keyFile)
59 | if err != nil {
60 | log.Errorf("stopped serving TLS (%s): %s", serveAddr, err)
61 | }
62 | }()
63 |
64 | log.Infof("fake API server listening and serving on %s", serveAddr)
65 |
66 | return compCh, nil
67 | }
68 |
69 | func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
70 | log.Infof("(%s) Fake API server received url %s", r.URL, r.RemoteAddr)
71 |
72 | log.Infof("(%s) Request headers:", r.RemoteAddr)
73 | for k, vs := range r.Header {
74 | for _, v := range vs {
75 | log.Infof("(%s) %s: %s", r.RemoteAddr, k, v)
76 | rw.Header().Add(k, v)
77 | }
78 | }
79 |
80 | body, err := ioutil.ReadAll(r.Body)
81 | if err != nil {
82 | log.Errorf("failed to read request body: %s", err)
83 | rw.WriteHeader(http.StatusInternalServerError)
84 | return
85 | }
86 |
87 | log.Infof("(%s) Request Body: %s", r.RemoteAddr, body)
88 |
89 | if _, err := rw.Write(body); err != nil {
90 | log.Errorf("failed to write request body to response: %s", err)
91 | rw.WriteHeader(http.StatusInternalServerError)
92 | return
93 | }
94 |
95 | rw.WriteHeader(http.StatusOK)
96 | }
97 |
--------------------------------------------------------------------------------
/pkg/proxy/tokenreview/tokenreview_test.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package tokenreview
3 |
4 | import (
5 | "errors"
6 | "net/http"
7 | "reflect"
8 | "testing"
9 |
10 | authv1 "k8s.io/api/authentication/v1"
11 |
12 | "github.com/jetstack/kube-oidc-proxy/pkg/proxy/tokenreview/fake"
13 | )
14 |
15 | type testT struct {
16 | reviewResp *authv1.TokenReview
17 | errResp error
18 |
19 | expAuth bool
20 | expErr error
21 | }
22 |
23 | func TestReview(t *testing.T) {
24 |
25 | tests := map[string]testT{
26 | "if a create fails then this error is returned": {
27 | reviewResp: nil,
28 | errResp: errors.New("create error response"),
29 | expAuth: false,
30 | expErr: errors.New("create error response"),
31 | },
32 |
33 | "if an error exists in the status of the response pass error back": {
34 | reviewResp: &authv1.TokenReview{
35 | Status: authv1.TokenReviewStatus{
36 | Error: "status error",
37 | },
38 | },
39 | errResp: nil,
40 | expAuth: false,
41 | expErr: errors.New("error authenticating using token review: status error"),
42 | },
43 |
44 | "if the response returns unauthenticated, return false": {
45 | reviewResp: &authv1.TokenReview{
46 | Status: authv1.TokenReviewStatus{
47 | Authenticated: false,
48 | },
49 | },
50 | errResp: nil,
51 | expAuth: false,
52 | expErr: nil,
53 | },
54 |
55 | "if the response returns authenticated, return true": {
56 | reviewResp: &authv1.TokenReview{
57 | Status: authv1.TokenReviewStatus{
58 | Authenticated: true,
59 | },
60 | },
61 | errResp: nil,
62 | expAuth: true,
63 | expErr: nil,
64 | },
65 | }
66 |
67 | for name, test := range tests {
68 | t.Run(name, func(t *testing.T) {
69 | runTest(t, test)
70 | })
71 | }
72 | }
73 |
74 | func runTest(t *testing.T, test testT) {
75 | tReviewer := &TokenReview{
76 | audiences: nil,
77 | reviewRequester: fake.New().WithCreate(test.reviewResp, test.errResp),
78 | }
79 |
80 | authed, err := tReviewer.Review(
81 | &http.Request{
82 | Header: map[string][]string{
83 | "Authorization": []string{"bearer test-token"},
84 | },
85 | },
86 | )
87 |
88 | if !reflect.DeepEqual(test.expErr, err) {
89 | t.Errorf("got unexpected error, exp=%v got=%v",
90 | test.expErr, err)
91 | }
92 |
93 | if test.expAuth != authed {
94 | t.Errorf("got unexpected authed, exp=%t got=%t",
95 | test.expAuth, authed)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | ## DCO Sign off
3 |
4 | All authors to the project retain copyright to their work. However, to ensure
5 | that they are only submitting work that they have rights to, we are requiring
6 | everyone to acknowledge this by signing their work.
7 |
8 | Any copyright notices in this repo should specify the authors as "the Jetstack
9 | kube-oidc-proxy contributors".
10 |
11 | To sign your work, just add a line like this at the end of your commit message:
12 |
13 | ```
14 | Signed-off-by: Joe Bloggs
15 | ```
16 |
17 | This can easily be done with the `--signoff` option to `git commit`.
18 | You can also mass sign-off a whole PR with `git rebase --signoff master`, replacing
19 | `master` with the branch you are creating a pull request again if not master.
20 |
21 | By doing this you state that you can certify the following (from https://developercertificate.org/):
22 |
23 | ```
24 | Developer Certificate of Origin
25 | Version 1.1
26 |
27 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
28 | 1 Letterman Drive
29 | Suite D4700
30 | San Francisco, CA, 94129
31 |
32 | Everyone is permitted to copy and distribute verbatim copies of this
33 | license document, but changing it is not allowed.
34 |
35 |
36 | Developer's Certificate of Origin 1.1
37 |
38 | By making a contribution to this project, I certify that:
39 |
40 | (a) The contribution was created in whole or in part by me and I
41 | have the right to submit it under the open source license
42 | indicated in the file; or
43 |
44 | (b) The contribution is based upon previous work that, to the best
45 | of my knowledge, is covered under an appropriate open source
46 | license and I have the right under that license to submit that
47 | work with modifications, whether created in whole or in part
48 | by me, under the same open source license (unless I am
49 | permitted to submit under a different license), as indicated
50 | in the file; or
51 |
52 | (c) The contribution was provided directly to me by some other
53 | person who certified (a), (b) or (c) and I have not modified
54 | it.
55 |
56 | (d) I understand and agree that this project and the contribution
57 | are public and that a record of the contribution (including all
58 | personal information I submit with it, including my sign-off) is
59 | maintained indefinitely and may be redistributed consistent with
60 | this project or the open source license(s) involved.
61 | ```
62 |
--------------------------------------------------------------------------------
/pkg/probe/probe_test.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package probe
3 |
4 | import (
5 | "context"
6 | "errors"
7 | "fmt"
8 | "net/http"
9 | "testing"
10 |
11 | "k8s.io/apiserver/pkg/authentication/authenticator"
12 |
13 | "github.com/jetstack/kube-oidc-proxy/pkg/util"
14 | )
15 |
16 | type fakeTokenAuthenticator struct {
17 | returnErr bool
18 | }
19 |
20 | var _ authenticator.Token = &fakeTokenAuthenticator{}
21 |
22 | func (f *fakeTokenAuthenticator) AuthenticateToken(context.Context, string) (*authenticator.Response, bool, error) {
23 | if f.returnErr {
24 | return nil, false, errors.New("foo bar authenticator not initialized")
25 | }
26 |
27 | return nil, false, errors.New("some other error")
28 | }
29 |
30 | func TestRun(t *testing.T) {
31 | f := &fakeTokenAuthenticator{
32 | returnErr: true,
33 | }
34 |
35 | port, err := util.FreePort()
36 | if err != nil {
37 | t.Error(err.Error())
38 | t.FailNow()
39 | }
40 |
41 | fakeJWT, err := util.FakeJWT("issuer")
42 | if err != nil {
43 | t.Error(err.Error())
44 | t.FailNow()
45 | }
46 |
47 | if err := Run(port, fakeJWT, f); err != nil {
48 | t.Error(err.Error())
49 | t.FailNow()
50 | }
51 |
52 | url := fmt.Sprintf("http://0.0.0.0:%s", port)
53 |
54 | var resp *http.Response
55 | var i int
56 |
57 | for {
58 | resp, err = http.Get(url + "/ready")
59 | if err == nil {
60 | break
61 | }
62 |
63 | if i >= 5 {
64 | t.Errorf("unexpected error: %s", err)
65 | t.FailNow()
66 | }
67 | i++
68 | }
69 |
70 | if resp.StatusCode != 503 {
71 | t.Errorf("expected ready probe to be responding and not ready, exp=%d got=%d",
72 | 503, resp.StatusCode)
73 | }
74 |
75 | f.returnErr = false
76 |
77 | resp, err = http.Get(url + "/ready")
78 | if err != nil {
79 | t.Fatalf("unexpected error: %s", err)
80 | }
81 |
82 | if resp.StatusCode != 200 {
83 | t.Errorf("expected ready probe to be responding and ready, exp=%d got=%d",
84 | 200, resp.StatusCode)
85 | }
86 |
87 | // Once the authenticator has returned with an non-initialised error, then
88 | // should always return ready
89 |
90 | f.returnErr = true
91 |
92 | resp, err = http.Get(url + "/ready")
93 | if err != nil {
94 | t.Fatalf("unexpected error: %s", err)
95 | }
96 |
97 | if resp.StatusCode != 200 {
98 | t.Errorf("expected ready probe to be responding and ready, exp=%d got=%d",
99 | 200, resp.StatusCode)
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/GenGitChangeLog.py:
--------------------------------------------------------------------------------
1 | # Copyright Jetstack Ltd. See LICENSE for details.
2 |
3 | # Generates kube-oidc-proxy Changelog
4 | # Call from the branch with 3 parameters:
5 | # 1. Date from which to start looking
6 | # 2. Github Token
7 |
8 | # requires python-dateutil and requests from pip
9 |
10 | from subprocess import *
11 | import re
12 | from datetime import datetime
13 | import dateutil.parser
14 | import sys
15 | import requests
16 |
17 |
18 |
19 | def parseIssues(message):
20 | issuesRet = []
21 | issues = re.findall('[#][0-9]+',message)
22 | if issues != None:
23 | for issue in issues:
24 | issuesRet.append(issue[1:])
25 | return issuesRet
26 |
27 |
28 | def f4(seq):
29 | # order preserving
30 | noDupes = []
31 | [noDupes.append(i) for i in seq if not noDupes.count(i)]
32 | return noDupes
33 |
34 |
35 |
36 |
37 |
38 |
39 | headers = {'Authorization':'token ' + sys.argv[2]}
40 |
41 |
42 | GIT_COMMIT_FIELDS = ['id', 'author_name', 'author_email', 'date', 'message']
43 | GIT_LOG_FORMAT = ['%H', '%an', '%ae', '%ai', '%s']
44 | GIT_LOG_FORMAT = '%x1f'.join(GIT_LOG_FORMAT) + '%x1e'
45 |
46 | #print repo.git.log(p=False)
47 |
48 | allIssues = []
49 |
50 | p = Popen('git log --format="%s" ' % GIT_LOG_FORMAT, shell=True, stdout=PIPE)
51 | (logb, _) = p.communicate()
52 | log = str(logb,"utf-8")
53 | log = log.strip('\n\x1e').split("\x1e")
54 | log = [row.strip().split("\x1f") for row in log]
55 | log = [dict(zip(GIT_COMMIT_FIELDS, row)) for row in log]
56 |
57 | notbefore = dateutil.parser.parse(sys.argv[1] + ' 00:00:00 -0400')
58 |
59 | for commit in log:
60 | created = dateutil.parser.parse(commit['date'])
61 | if created > notbefore:
62 | message = commit['message']
63 | allIssues.extend(parseIssues(message))
64 |
65 |
66 | allIssues = f4(allIssues)
67 |
68 | bylabels = {}
69 |
70 | for issue in allIssues:
71 | issueURL = 'https://api.github.com/repos/TremoloSecurity/kube-oidc-proxy/issues/' + issue
72 | r = requests.get(issueURL,headers=headers)
73 | json = r.json();
74 |
75 | if "labels" in json:
76 | for label in json['labels']:
77 | if not (label['name'] in bylabels):
78 | labelGroup = []
79 | bylabels[label["name"]] = labelGroup
80 | labelGroup = bylabels[label['name']]
81 | labelGroup.append(json)
82 |
83 |
84 | for label in bylabels:
85 | print('**' + label + 's:**')
86 | for issue in bylabels[label]:
87 | print(' - ' + issue['title'] + ' [\\#' + str(issue['number']) + '](' + issue['html_url'] + ')')
88 | print()
89 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.0.9
2 |
3 | **tasks:**
4 | - 1.0.9 [\#64](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/64)
5 |
6 | **enhancements:**
7 | - Add flags to be able to configure kubernetes client throttling [\#65](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/65)
8 |
9 | # 1.0.8
10 |
11 | **tasks:**
12 | - 1.0.8 [\#61](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/61)
13 |
14 | # 1.0.7
15 |
16 | **enhancements:**
17 | - change oidc config to line up with new kube authenticator [\#55](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/55)
18 |
19 | **tasks:**
20 | - 1.0.7 Release [\#54](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/54)
21 |
22 | # 1.0.6
23 |
24 | **bugs:**
25 | - e2e tests failing to complete [\#45](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/45)
26 | - Auditing is not working anymore [\#39](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/39)
27 |
28 | **tasks:**
29 | - 1.0.6 build [\#41](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/41)
30 |
31 | # 1.0.5
32 |
33 | **tasks:**
34 | - 1.0.5 build [\#34](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/34)
35 |
36 | # 1.0.4
37 |
38 | **tasks:**
39 | - 1.0.4 build [\#29](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/29)
40 |
41 | # 1.0.3
42 |
43 | **enhancements:**
44 | - 1.0.3 release [\#26](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/26)
45 |
46 | # 1.0.2
47 |
48 | **bugs:**
49 | - CVE-2022-1996 [\#20](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/20)
50 |
51 | # 1.0.1
52 |
53 | **enhancements:**
54 | - 1.0.1 [\#14](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/14)
55 |
56 | **bugs:**
57 | - fix timing issues in e2e tests [\#18](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/18)
58 | - runtime error: slice bounds out of range [:-2] [\#17](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/17)
59 |
60 | # 1.0.0
61 |
62 | **enhancements:**
63 | - 1.0.0 Release [\#10](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/10)
64 | - Access logging to standard out [\#2](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/2)
65 | - create github action to automate builds [\#8](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/8)
66 | - Switch from alpine --> ubuntu 20.04 [\#9](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/9)
67 | - Support `kubectl --as` [\#3](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/3)
68 | - Upgrade KinD [\#1](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/1)
69 |
70 | **bugs:**
71 | - update dependencies [\#5](https://github.com/TremoloSecurity/kube-oidc-proxy/issues/5)
72 |
73 |
--------------------------------------------------------------------------------
/patchlog.txt:
--------------------------------------------------------------------------------
1 | # Automatic Patch Log
2 |
3 | Automatic Update - 20211216T025617.851Z
4 | Automatic Update - 20220311T201831.205Z
5 | Automatic Update - 20220318T020336.250Z
6 | Automatic Update - 20220403T020307.625Z
7 | Automatic Update - 20220415T020318.763Z
8 | Automatic Update - 20220422T020314.907Z
9 | Automatic Update - 20220429T020319.615Z
10 | Automatic Update - 20220506T020321.057Z
11 | Automatic Update - 20220519T020313.080Z
12 | Automatic Update - 20220529T020344.385Z
13 | Automatic Update - 20220609T020332.886Z
14 | Automatic Update - 20220624T020345.647Z
15 | Automatic Update - 20220707T020312.276Z
16 | Automatic Update - 20220807T020800.447Z
17 | Automatic Update - 20220925T020336.718Z
18 | Automatic Update - 20221014T020348.280Z
19 | Automatic Update - 20221020T020349.425Z
20 | Automatic Update - 20221130T020413.421Z
21 | Automatic Update - 20221211T020437.196Z
22 | Automatic Update - 20230131T020526.940Z
23 | Automatic Update - 20230214T020526.050Z
24 | Automatic Update - 20230306T020416.148Z
25 | Automatic Update - 20230310T020312.894Z
26 | Automatic Update - 20230427T020320.801Z
27 | Automatic Update - 20230525T020428.881Z
28 | Automatic Update - 20230601T020342.442Z
29 | Automatic Update - 20230607T020351.116Z
30 | Automatic Update - 20230616T020248.766Z
31 | Automatic Update - 20231005T020301.893Z
32 | Automatic Update - 20231026T020312.403Z
33 | Automatic Update - 20231108T020319.537Z
34 | Automatic Update - 20231116T020337.574Z
35 | Automatic Update - 20231123T020342.310Z
36 | Automatic Update - 20231209T020329.153Z
37 | Automatic Update - 20231213T020333.022Z
38 | Automatic Update - 20240119T020325.770Z
39 | Automatic Update - 20240124T020357.219Z
40 | Automatic Update - 20240207T020401.740Z
41 | Automatic Update - 20240217T020411.845Z
42 | Automatic Update - 20240320T020336.148Z
43 | Automatic Update - 20240329T020424.531Z
44 | Automatic Update - 20240417T020415.214Z
45 | Automatic Update - 20240420T020411.899Z
46 | Automatic Update - 20240602T020353.512Z
47 | Automatic Update - 20240629T020356.989Z
48 | Automatic Update - 20240802T020420.511Z
49 | Automatic Update - 20240810T020429.835Z
50 |
51 | force rebuild 2024-09-15
52 | Automatic Update - 20250208T020419.731Z
53 | Automatic Update - 20250220T020420.716Z
54 | Automatic Update - 20250222T020423.982Z
55 | Automatic Update - 20250226T020423.793Z
56 | Automatic Update - 20250305T020424.511Z
57 | Automatic Update - 20250405T020413.953Z
58 | Automatic Update - 20250416T020355.722Z
59 | Automatic Update - 20250530T020358.267Z
60 | Automatic Update - 20250611T020416.398Z
61 | Automatic Update - 20250620T020418.225Z
62 | Automatic Update - 20250921T020340.284Z
63 | Automatic Update - 20250924T020329.316Z
64 | Automatic Update - 20250930T020342.328Z
65 | Automatic Update - 20251002T020421.143Z
--------------------------------------------------------------------------------
/test/e2e/suite/cases/token/token.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package token
3 |
4 | import (
5 | "bytes"
6 | "context"
7 | "fmt"
8 | "net/http"
9 | "net/url"
10 | "time"
11 |
12 | . "github.com/onsi/ginkgo"
13 | . "github.com/onsi/gomega"
14 |
15 | k8sErrors "k8s.io/apimachinery/pkg/api/errors"
16 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17 |
18 | "github.com/jetstack/kube-oidc-proxy/test/e2e/framework"
19 | )
20 |
21 | var _ = framework.CasesDescribe("Token", func() {
22 | f := framework.NewDefaultFramework("token")
23 |
24 | It("should error when tokens are wrong for the issuer", func() {
25 | By("No token should error")
26 | expectProxyUnauthorized(f, nil)
27 |
28 | By("Bad token should error")
29 | expectProxyUnauthorized(f, []byte("bad token"))
30 |
31 | By("Wrong issuer should error")
32 | badURL, err := url.Parse("incorrect-issuer.io")
33 | Expect(err).NotTo(HaveOccurred())
34 |
35 | expectProxyUnauthorized(f, f.Helper().NewTokenPayload(
36 | badURL, f.ClientID(), time.Now().Add(time.Minute)))
37 |
38 | By("Wrong audience should error")
39 | expectProxyUnauthorized(f, f.Helper().NewTokenPayload(
40 | f.IssuerURL(), "wrong-aud", time.Now().Add(time.Minute)))
41 |
42 | By("Token expires now")
43 | expectProxyUnauthorized(f, f.Helper().NewTokenPayload(
44 | f.IssuerURL(), f.ClientID(), time.Now()))
45 |
46 | By("Valid token should return Kubernetes forbidden")
47 | client := f.NewProxyClient()
48 |
49 | // If does not return with Kubernetes forbidden error then error
50 | _, err = client.CoreV1().Pods(f.Namespace.Name).List(context.TODO(), metav1.ListOptions{})
51 | if !k8sErrors.IsForbidden(err) {
52 | Expect(err).NotTo(HaveOccurred())
53 | }
54 | })
55 | })
56 |
57 | func expectProxyUnauthorized(f *framework.Framework, tokenPayload []byte) {
58 | // Build client using given token payload
59 | signedToken, err := f.Helper().SignToken(f.IssuerKeyBundle(), tokenPayload)
60 | Expect(err).NotTo(HaveOccurred())
61 |
62 | proxyConfig := f.NewProxyRestConfig()
63 | requester := f.Helper().NewRequester(proxyConfig.Transport, signedToken)
64 |
65 | // Send request with signed token to proxy
66 | target := fmt.Sprintf("%s/api/v1/namespaces/%s/pods",
67 | proxyConfig.Host, f.Namespace.Name)
68 |
69 | body, resp, err := requester.Get(target)
70 | body = bytes.TrimSpace(body)
71 | Expect(err).NotTo(HaveOccurred())
72 |
73 | // Check body and status code the token was rejected
74 | if resp.StatusCode != http.StatusUnauthorized ||
75 | !bytes.Equal(body, []byte("Unauthorized")) {
76 | Expect(fmt.Errorf("expected status code %d with body Unauthorized, got= %d %q",
77 | http.StatusUnauthorized, resp.StatusCode, body)).NotTo(HaveOccurred())
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/demo/manifests/components/base32.libsonnet:
--------------------------------------------------------------------------------
1 | {
2 | local base32_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
3 | local base32_inv = { [base32_table[i]]: i for i in std.range(0, 31) },
4 |
5 |
6 | base32(input)::
7 | local bytes =
8 | if std.type(input) == 'string' then
9 | std.map(function(c) std.codepoint(c), input)
10 | else
11 | input;
12 |
13 | local oneChar(arr, i) =
14 | // 5 MSB of i
15 | base32_table[(arr[i] & 248) >> 3];
16 |
17 | local twoChars(arr, i) =
18 | oneChar(arr, i) +
19 | // 3 LSB of i, 2 MSB of i+1
20 | base32_table[(arr[i] & 7) << 2 | (arr[i + 1] & 192) >> 6] +
21 | // 5 NSB of i+1
22 | base32_table[(arr[i + 1] & 62) >> 1];
23 |
24 | local threeChars(arr, i) =
25 | twoChars(arr, i) +
26 | // 1 LSB of i+1, 4 MSB of i+2
27 | base32_table[(arr[i + 1] & 1) << 4 | (arr[i + 2] & 240) >> 4];
28 |
29 | local fourChars(arr, i) =
30 | threeChars(arr, i) +
31 | // 4 LSB of i+2 + 1 MSB of i+3
32 | base32_table[(arr[i + 2] & 15) << 1 | (arr[i + 3] & 128) >> 7] +
33 | // 5 NSB of i+3
34 | base32_table[(arr[i + 3] & 124) >> 2];
35 |
36 | local aux(arr, i, r) =
37 | if i >= std.length(arr) then
38 | r
39 | else if i + 1 >= std.length(arr) then
40 | local str =
41 | oneChar(arr, i) +
42 | // 3 LSB of i
43 | base32_table[(arr[i] & 7) << 2] +
44 | '======';
45 | aux(arr, i + 5, r + str) tailstrict
46 | else if i + 2 >= std.length(arr) then
47 | local str =
48 | twoChars(arr, i) +
49 | // 1 LSB of i+1
50 | base32_table[(arr[i + 1] & 1) << 4] +
51 | '====';
52 | aux(arr, i + 5, r + str) tailstrict
53 | else if i + 3 >= std.length(arr) then
54 | local str =
55 | threeChars(arr, i) +
56 | // 4 LSB of i+2
57 | base32_table[(arr[i + 2] & 15) << 1] +
58 | '===';
59 | aux(arr, i + 5, r + str) tailstrict
60 | else if i + 4 >= std.length(arr) then
61 | local str =
62 | fourChars(arr, i) +
63 | // 2 LSB of i+3
64 | base32_table[(arr[i + 3] & 3) << 3] +
65 | '=';
66 | aux(arr, i + 5, r + str) tailstrict
67 | else
68 | local str =
69 | fourChars(arr, i) +
70 | // 2 LSB of i+3, 3 MSB of i+4
71 | base32_table[(arr[i + 3] & 3) << 3 | (arr[i + 4] & 224) >> 5] +
72 | // 5 LSB
73 | base32_table[(arr[i + 4] & 31)];
74 | aux(arr, i + 5, r + str) tailstrict;
75 |
76 | local sanity = std.foldl(function(r, a) r && (a < 256), bytes, true);
77 | if !sanity then
78 | error 'Can only base32 encode strings / arrays of single bytes.'
79 | else
80 | aux(bytes, 0, ''),
81 |
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/test/environment/environment.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package environment
3 |
4 | import (
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 |
9 | "k8s.io/client-go/kubernetes"
10 | "sigs.k8s.io/kind/pkg/cluster/nodes"
11 |
12 | "github.com/jetstack/kube-oidc-proxy/test/kind"
13 | )
14 |
15 | const (
16 | defaultNodeImage = "1.32.0"
17 | defaultRootPath = "../../."
18 | )
19 |
20 | type Environment struct {
21 | kind *kind.Kind
22 |
23 | rootPath string
24 | nodeImage string
25 | }
26 |
27 | func New(masterNodesCount, workerNodesCount int) (*Environment, error) {
28 | nodeImage := os.Getenv("KUBE_OIDC_PROXY_K8S_VERSION")
29 | if nodeImage == "" {
30 | nodeImage = defaultNodeImage
31 | }
32 | nodeImage = fmt.Sprintf("kindest/node:v%s", nodeImage)
33 |
34 | rootPath, err := RootPath()
35 | if err != nil {
36 | return nil, err
37 | }
38 |
39 | kind := kind.New(rootPath, nodeImage, masterNodesCount, workerNodesCount)
40 |
41 | return &Environment{
42 | rootPath: rootPath,
43 | nodeImage: nodeImage,
44 | kind: kind,
45 | }, nil
46 | }
47 |
48 | func (e *Environment) Create() error {
49 | if err := e.kind.Create(); err != nil {
50 | return fmt.Errorf("failed to create kind cluster: %s", err)
51 | }
52 |
53 | if err := e.kind.LoadAllImages(); err != nil {
54 | return err
55 | }
56 |
57 | return nil
58 | }
59 |
60 | func (e *Environment) Destory() error {
61 | if e.kind != nil {
62 | if err := e.kind.Destroy(); err != nil {
63 | return err
64 | }
65 | }
66 |
67 | return nil
68 | }
69 |
70 | func (e *Environment) KubeClient() *kubernetes.Clientset {
71 | return e.kind.KubeClient()
72 | }
73 |
74 | func (e *Environment) KubeConfigPath() string {
75 | return e.kind.KubeConfigPath()
76 | }
77 |
78 | func (e *Environment) RootPath() string {
79 | return e.rootPath
80 | }
81 |
82 | func (e *Environment) Node(name string) (*nodes.Node, error) {
83 | ns, err := e.kind.Nodes()
84 | if err != nil {
85 | return nil, err
86 | }
87 |
88 | var node *nodes.Node
89 | for _, n := range ns {
90 | if n.String() == name {
91 | node = &n
92 | break
93 | }
94 | }
95 |
96 | if node == nil {
97 | return nil, fmt.Errorf("failed to find node %q", name)
98 | }
99 |
100 | return node, nil
101 | }
102 |
103 | func (e *Environment) Kind() *kind.Kind {
104 | return e.kind
105 | }
106 |
107 | func RootPath() (string, error) {
108 | rootPath := os.Getenv("KUBE_OIDC_PROXY_ROOT_PATH")
109 | if rootPath == "" {
110 | rootPath = defaultRootPath
111 | }
112 |
113 | rootPath, err := filepath.Abs(rootPath)
114 | if err != nil {
115 | return "", fmt.Errorf("failed to get absolute path %q: %s",
116 | rootPath, err)
117 | }
118 |
119 | return rootPath, nil
120 | }
121 |
--------------------------------------------------------------------------------
/pkg/proxy/logging/accesslog.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package logging
3 |
4 | import (
5 | "fmt"
6 | "net/http"
7 | "strings"
8 | "time"
9 |
10 | "k8s.io/apiserver/pkg/authentication/user"
11 | )
12 |
13 | const (
14 | UserHeaderClientIPKey = "Remote-Client-IP"
15 | timestampLayout = "2006-01-02T15:04:05-0700"
16 | )
17 |
18 | // logs the request
19 | func LogSuccessfulRequest(req *http.Request, inboundUser user.Info, outboundUser user.Info) {
20 | remoteAddr := req.RemoteAddr
21 | indexOfColon := strings.Index(remoteAddr, ":")
22 | if indexOfColon > 0 {
23 | remoteAddr = remoteAddr[0:indexOfColon]
24 | }
25 |
26 | inboundExtras := ""
27 |
28 | if inboundUser.GetExtra() != nil {
29 | for key, value := range inboundUser.GetExtra() {
30 | inboundExtras += key + "=" + strings.Join(value, "|") + " "
31 | }
32 | }
33 |
34 | outboundUserLog := ""
35 |
36 | if outboundUser != nil {
37 | outboundExtras := ""
38 |
39 | if outboundUser.GetExtra() != nil {
40 | for key, value := range outboundUser.GetExtra() {
41 | outboundExtras += key + "=" + strings.Join(value, "|") + " "
42 | }
43 | }
44 |
45 | outboundUserLog = fmt.Sprintf(" outbound:[%s / %s / %s / %s]", outboundUser.GetName(), strings.Join(outboundUser.GetGroups(), "|"), outboundUser.GetUID(), outboundExtras)
46 | }
47 |
48 | xFwdFor := findXForwardedFor(req.Header, remoteAddr)
49 |
50 | fmt.Printf("[%s] AuSuccess src:[%s / % s] URI:%s inbound:[%s / %s / %s]%s\n", time.Now().Format(timestampLayout), remoteAddr, xFwdFor, req.RequestURI, inboundUser.GetName(), strings.Join(inboundUser.GetGroups(), "|"), inboundExtras, outboundUserLog)
51 | }
52 |
53 | // determines if the x-forwarded-for header is present, if so remove
54 | // the remoteaddr since it is repetitive
55 | func findXForwardedFor(headers http.Header, remoteAddr string) string {
56 | xFwdFor := headers.Get("x-forwarded-for")
57 | // clean off remoteaddr from x-forwarded-for
58 | if xFwdFor != "" {
59 |
60 | newXFwdFor := ""
61 | oneFound := false
62 | xFwdForIps := strings.Split(xFwdFor, ",")
63 |
64 | for _, ip := range xFwdForIps {
65 | ip = strings.TrimSpace(ip)
66 |
67 | if ip != remoteAddr {
68 | newXFwdFor = newXFwdFor + ip + ", "
69 | oneFound = true
70 | }
71 |
72 | }
73 |
74 | if oneFound {
75 | newXFwdFor = newXFwdFor[0 : len(newXFwdFor)-2]
76 | }
77 |
78 | xFwdFor = newXFwdFor
79 |
80 | }
81 |
82 | return xFwdFor
83 | }
84 |
85 | // logs the failed request
86 | func LogFailedRequest(req *http.Request) {
87 | remoteAddr := req.RemoteAddr
88 | indexOfColon := strings.Index(remoteAddr, ":")
89 | if indexOfColon > 0 {
90 | remoteAddr = remoteAddr[0:indexOfColon]
91 | }
92 |
93 | fmt.Printf("[%s] AuFail src:[%s / % s] URI:%s\n", time.Now().Format(timestampLayout), remoteAddr, req.Header.Get(("x-forwarded-for")), req.RequestURI)
94 | }
95 |
--------------------------------------------------------------------------------
/cmd/app/options/options.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "errors"
6 | "fmt"
7 |
8 | "github.com/spf13/cobra"
9 | "golang.org/x/term"
10 | k8sErrors "k8s.io/apimachinery/pkg/util/errors"
11 |
12 | cliflag "k8s.io/component-base/cli/flag"
13 | )
14 |
15 | const (
16 | AppName = "kube-oidc-proxy"
17 | )
18 |
19 | type Options struct {
20 | App *KubeOIDCProxyOptions
21 | OIDCAuthentication *OIDCAuthenticationOptions
22 | SecureServing *SecureServingOptions
23 | Audit *AuditOptions
24 | Client *ClientOptions
25 | Misc *MiscOptions
26 |
27 | nfs *cliflag.NamedFlagSets
28 | }
29 |
30 | func New() *Options {
31 | nfs := new(cliflag.NamedFlagSets)
32 |
33 | // Add flags to command sets
34 | return &Options{
35 | App: NewKubeOIDCProxyOptions(nfs),
36 | OIDCAuthentication: NewOIDCAuthenticationOptions(nfs),
37 | SecureServing: NewSecureServingOptions(nfs),
38 | Audit: NewAuditOptions(nfs),
39 | Client: NewClientOptions(nfs),
40 | Misc: NewMiscOptions(nfs),
41 |
42 | nfs: nfs,
43 | }
44 | }
45 |
46 | func (o *Options) AddFlags(cmd *cobra.Command) {
47 | // pretty output from kube-apiserver
48 | usageFmt := "Usage:\n %s\n"
49 | cols, _, _ := term.GetSize(0)
50 | cmd.SetUsageFunc(func(cmd *cobra.Command) error {
51 | fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine())
52 | cliflag.PrintSections(cmd.OutOrStderr(), *o.nfs, cols)
53 | return nil
54 | })
55 |
56 | cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
57 | fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine())
58 | cliflag.PrintSections(cmd.OutOrStdout(), *o.nfs, cols)
59 | })
60 |
61 | fs := cmd.Flags()
62 | for _, f := range o.nfs.FlagSets {
63 | fs.AddFlagSet(f)
64 | }
65 | }
66 |
67 | func (o *Options) Validate(cmd *cobra.Command) error {
68 | if cmd.Flag("version").Value.String() == "true" {
69 | o.Misc.PrintVersionAndExit()
70 | }
71 |
72 | var errs []error
73 |
74 | if err := o.OIDCAuthentication.Validate(); err != nil {
75 | errs = append(errs, err)
76 | }
77 |
78 | if err := o.SecureServing.Validate(); len(err) > 0 {
79 | errs = append(errs, err...)
80 | }
81 |
82 | if o.SecureServing.BindPort == o.App.ReadinessProbePort {
83 | errs = append(errs, errors.New("unable to securely serve on port 8080 (used by readiness probe)"))
84 | }
85 |
86 | if err := o.Audit.Validate(); len(err) > 0 {
87 | errs = append(errs, err...)
88 | }
89 |
90 | if o.App.DisableImpersonation &&
91 | (o.App.ExtraHeaderOptions.EnableClientIPExtraUserHeader || len(o.App.ExtraHeaderOptions.ExtraUserHeaders) > 0) {
92 | errs = append(errs, errors.New("cannot add extra user headers when impersonation disabled"))
93 | }
94 |
95 | if len(errs) > 0 {
96 | return k8sErrors.NewAggregate(errs)
97 | }
98 |
99 | return nil
100 | }
101 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/README.md:
--------------------------------------------------------------------------------
1 | # kube-oidc-proxy helm chart
2 |
3 | This is a `helm` chart that installs [`kube-oidc-proxy`](https://github.com/jetstack/kube-oidc-proxy/).
4 | This helm chart cannot be installed out of the box without providing own
5 | configuration.
6 |
7 | This helm chart is based on example configuration provided in `kube-oidc-proxy`
8 | [repository](https://github.com/jetstack/kube-oidc-proxy/blob/master/deploy/yaml/kube-oidc-proxy.yaml).
9 |
10 | Minimal required configuration is `oidc` section of `value.yaml` file.
11 |
12 | ```yaml
13 | oidc:
14 | clientId: my-client
15 | issuerUrl: https://accounts.google.com
16 | usernameClaim: email
17 | ```
18 |
19 | When a custom root CA certificate is required it should be added as PEM encoded
20 | text value:
21 |
22 | ```yaml
23 | oidc:
24 | caPEM: |
25 | -----BEGIN CERTIFICATE-----
26 | MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
27 | A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
28 | b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
29 | MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
30 | YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
31 | aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
32 | jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
33 | xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
34 | 1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
35 | snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
36 | U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
37 | 9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
38 | BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
39 | AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
40 | yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
41 | 38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
42 | AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
43 | DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
44 | HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
45 | -----END CERTIFICATE-----
46 | ```
47 |
48 | This minimal configuration gives a cluster internal IP address that can be used
49 | with `kubectl` to authenticate requests to Kubernetes API server.
50 |
51 | The service can be exposed via ingress controller and give access to external
52 | clients. Example of exposing via ingress controller.
53 |
54 | ```yaml
55 | ingress:
56 | enabled: true
57 | annotations:
58 | kubernetes.io/ingress.class: traefik
59 | traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip
60 | hosts:
61 | - host: ""
62 | paths:
63 | - /oidc-proxy
64 | ```
65 |
66 | By default the helm chart will create self-signed TLS certificate for `kube-oidc-proxy`
67 | service. It is possible to provide secret name that contains TLS artifacts for
68 | service. The secret must be of `kubernetes.io/tls` type.
69 |
70 | ```yaml
71 | tls:
72 | secretName: my-tls-secret-with-key-and-cert
73 | ```
74 |
--------------------------------------------------------------------------------
/test/tools/audit-webhook/pkg/sink/sink.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package sink
3 |
4 | import (
5 | "crypto/rsa"
6 | "crypto/x509"
7 | "encoding/json"
8 | "encoding/pem"
9 | "fmt"
10 | "io/ioutil"
11 | "net"
12 | "net/http"
13 | "os"
14 | "sync"
15 |
16 | log "github.com/sirupsen/logrus"
17 | auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
18 | )
19 |
20 | type Sink struct {
21 | logPath string
22 | keyFile, certFile string
23 |
24 | sk *rsa.PrivateKey
25 |
26 | stopCh <-chan struct{}
27 | mu sync.Mutex
28 | }
29 |
30 | func New(logPath, keyFile, certFile string, stopCh <-chan struct{}) (*Sink, error) {
31 | b, err := ioutil.ReadFile(keyFile)
32 | if err != nil {
33 | return nil, err
34 | }
35 |
36 | block, _ := pem.Decode(b)
37 | if block == nil {
38 | return nil,
39 | fmt.Errorf("failed to parse PEM block containing the key: %q", keyFile)
40 | }
41 |
42 | sk, err := x509.ParsePKCS1PrivateKey(block.Bytes)
43 | if err != nil {
44 | return nil, err
45 | }
46 |
47 | return &Sink{
48 | logPath: logPath,
49 | keyFile: keyFile,
50 | certFile: certFile,
51 | sk: sk,
52 | stopCh: stopCh,
53 | }, nil
54 | }
55 |
56 | func (s *Sink) Run(bindAddress, listenPort string) (<-chan struct{}, error) {
57 | serveAddr := fmt.Sprintf("%s:%s", bindAddress, listenPort)
58 |
59 | l, err := net.Listen("tcp", serveAddr)
60 | if err != nil {
61 | return nil, err
62 | }
63 |
64 | go func() {
65 | <-s.stopCh
66 | if l != nil {
67 | l.Close()
68 | }
69 | }()
70 |
71 | compCh := make(chan struct{})
72 | go func() {
73 | defer close(compCh)
74 |
75 | err := http.ServeTLS(l, s, s.certFile, s.keyFile)
76 | if err != nil {
77 | log.Errorf("stopped serving TLS (%s): %s", serveAddr, err)
78 | }
79 | }()
80 |
81 | log.Infof("audit webhook listening and serving on %s", serveAddr)
82 |
83 | return compCh, nil
84 | }
85 |
86 | func (s *Sink) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
87 | log.Infof("%s: audit webhook received url %s", r.RemoteAddr, r.URL)
88 |
89 | var events auditv1.EventList
90 | err := json.NewDecoder(r.Body).Decode(&events)
91 | if err != nil {
92 | log.Errorf("%s: failed to decode request body: %s", r.RemoteAddr, err)
93 | http.Error(rw, err.Error(), http.StatusBadRequest)
94 | return
95 | }
96 | log.Infof("%s: got events: %v", r.RemoteAddr, events)
97 |
98 | s.mu.Lock()
99 | defer s.mu.Unlock()
100 |
101 | f, err := os.OpenFile(s.logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
102 | if err != nil {
103 | log.Errorf("%s: failed to open log file: %s", r.RemoteAddr, err)
104 | http.Error(rw, err.Error(), http.StatusBadRequest)
105 | return
106 | }
107 | defer f.Close()
108 |
109 | for _, event := range events.Items {
110 | if err := json.NewEncoder(f).Encode(event); err != nil {
111 | log.Errorf("%s: failed to write audit event: %s", r.RemoteAddr, err)
112 | http.Error(rw, err.Error(), http.StatusBadRequest)
113 | return
114 | }
115 | }
116 |
117 | rw.WriteHeader(http.StatusOK)
118 | }
119 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/oauth2-proxy.jsonnet:
--------------------------------------------------------------------------------
1 | /*
2 | * Bitnami Kubernetes Production Runtime - A collection of services that makes it
3 | * easy to run production workloads in Kubernetes.
4 | *
5 | * Copyright 2018-2019 Bitnami
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | local kube = import "../lib/kube.libsonnet";
21 |
22 | local OAUTH2_PROXY_IMAGE = (import "images.json")["oauth2_proxy"];
23 |
24 | {
25 | p:: "",
26 | metadata:: {
27 | metadata+: {
28 | namespace: "kubeprod",
29 | },
30 | },
31 |
32 | secret: kube.Secret($.p + "oauth2-proxy") + $.metadata {
33 | data_+: {
34 | client_id: error "client_id is required",
35 | client_secret: error "client_secret is required",
36 | cookie_secret: error "cookie_secret is required",
37 | },
38 | },
39 |
40 | svc: kube.Service($.p + "oauth2-proxy") + $.metadata {
41 | target_pod: $.deploy.spec.template,
42 | port: 4180,
43 | },
44 |
45 | hpa: kube.HorizontalPodAutoscaler($.p + "oauth2-proxy") + $.metadata {
46 | target: $.deploy,
47 | spec+: {maxReplicas: 10},
48 | },
49 |
50 | deploy: kube.Deployment($.p + "oauth2-proxy") + $.metadata {
51 | spec+: {
52 | template+: {
53 | spec+: {
54 | containers_+: {
55 | proxy: kube.Container("oauth2-proxy") {
56 | image: OAUTH2_PROXY_IMAGE,
57 | args_+: {
58 | "email-domain": "*",
59 | "http-address": "0.0.0.0:4180",
60 | "cookie-secure": "true",
61 | "cookie-refresh": "3h",
62 | "set-xauthrequest": true,
63 | "tls-cert": "",
64 | "upstream": "file:///dev/null",
65 | },
66 | env_+: {
67 | OAUTH2_PROXY_CLIENT_ID: kube.SecretKeyRef($.secret, "client_id"),
68 | OAUTH2_PROXY_CLIENT_SECRET: kube.SecretKeyRef($.secret, "client_secret"),
69 | OAUTH2_PROXY_COOKIE_SECRET: kube.SecretKeyRef($.secret, "cookie_secret"),
70 | },
71 | ports_+: {
72 | http: {containerPort: 4180},
73 | },
74 | readinessProbe: {
75 | httpGet: {path: "/ping", port: "http"},
76 | },
77 | livenessProbe: self.readinessProbe {
78 | initialDelaySeconds: 30,
79 | },
80 | resources+: {
81 | requests+: {cpu: "10m"},
82 | },
83 | },
84 | },
85 | },
86 | },
87 | },
88 | },
89 | }
90 |
--------------------------------------------------------------------------------
/cmd/app/options/oidc.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "fmt"
6 |
7 | "github.com/spf13/pflag"
8 |
9 | cliflag "k8s.io/component-base/cli/flag"
10 | )
11 |
12 | type OIDCAuthenticationOptions struct {
13 | CAFile string
14 | ClientID string
15 | IssuerURL string
16 | UsernameClaim string
17 | UsernamePrefix string
18 | GroupsClaim string
19 | GroupsPrefix string
20 | SigningAlgs []string
21 | RequiredClaims map[string]string
22 | }
23 |
24 | func NewOIDCAuthenticationOptions(nfs *cliflag.NamedFlagSets) *OIDCAuthenticationOptions {
25 | return new(OIDCAuthenticationOptions).AddFlags(nfs.FlagSet("OIDC"))
26 | }
27 |
28 | func (o *OIDCAuthenticationOptions) Validate() error {
29 | if o != nil && (len(o.IssuerURL) > 0) != (len(o.ClientID) > 0) {
30 | return fmt.Errorf("oidc-issuer-url and oidc-client-id should be specified together")
31 | }
32 |
33 | return nil
34 | }
35 |
36 | func (o *OIDCAuthenticationOptions) AddFlags(fs *pflag.FlagSet) *OIDCAuthenticationOptions {
37 | fs.StringVar(&o.IssuerURL, "oidc-issuer-url", o.IssuerURL, ""+
38 | "The URL of the OpenID issuer, only HTTPS scheme will be accepted.")
39 |
40 | fs.StringVar(&o.ClientID, "oidc-client-id", o.ClientID,
41 | "The client ID for the OpenID Connect client.")
42 |
43 | fs.StringVar(&o.CAFile, "oidc-ca-file", o.CAFile, ""+
44 | "The OpenID server's certificate will be verified by one of the authorities "+
45 | "in the oidc-ca-file, otherwise the host's root CA set will be used")
46 |
47 | fs.StringVar(&o.UsernameClaim, "oidc-username-claim", "sub", ""+
48 | "The OpenID claim to use as the username. Note that claims other than the default ('sub') "+
49 | "is not guaranteed to be unique and immutable")
50 |
51 | fs.StringVar(&o.UsernamePrefix, "oidc-username-prefix", "", ""+
52 | "If provided, all usernames will be prefixed with this value. If not provided, "+
53 | "username claims other than 'email' are prefixed by the issuer URL to avoid "+
54 | "clashes. To skip any prefixing, provide the value '-'.")
55 |
56 | fs.StringVar(&o.GroupsClaim, "oidc-groups-claim", "", ""+
57 | "If provided, the name of a custom OpenID Connect claim for specifying user groups. "+
58 | "The claim value is expected to be a string or array of strings.")
59 |
60 | fs.StringVar(&o.GroupsPrefix, "oidc-groups-prefix", "", ""+
61 | "If provided, all groups will be prefixed with this value to prevent conflicts with "+
62 | "other authentication strategies.")
63 |
64 | fs.StringSliceVar(&o.SigningAlgs, "oidc-signing-algs", []string{"RS256"}, ""+
65 | "Comma-separated list of allowed JOSE asymmetric signing algorithms. JWTs with a "+
66 | "'alg' header value not in this list will be rejected. "+
67 | "Values are defined by RFC 7518 https://tools.ietf.org/html/rfc7518#section-3.1.")
68 |
69 | fs.Var(cliflag.NewMapStringStringNoSplit(&o.RequiredClaims), "oidc-required-claim", ""+
70 | "A key=value pair that describes a required claim in the ID Token. "+
71 | "If set, the claim is verified to be present in the ID Token with a matching value. "+
72 | "Repeat this flag to specify multiple claims.")
73 |
74 | return o
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/proxy/audit/audit.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package audit
3 |
4 | import (
5 | "fmt"
6 | "net/http"
7 |
8 | "k8s.io/apimachinery/pkg/util/sets"
9 | genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
10 | "k8s.io/apiserver/pkg/server"
11 | genericfilters "k8s.io/apiserver/pkg/server/filters"
12 | "k8s.io/component-base/version"
13 |
14 | "github.com/jetstack/kube-oidc-proxy/cmd/app/options"
15 | )
16 |
17 | type Audit struct {
18 | opts *options.AuditOptions
19 | serverConfig *server.CompletedConfig
20 | }
21 |
22 | // New creates a new Audit struct to handle auditing for proxy requests. This
23 | // is mostly a wrapper for the apiserver auditing handlers to combine them with
24 | // the proxy.
25 | func New(opts *options.AuditOptions, externalAddress string, secureServingInfo *server.SecureServingInfo) (*Audit, error) {
26 | serverConfig := &server.Config{
27 | ExternalAddress: externalAddress,
28 | SecureServing: secureServingInfo,
29 |
30 | // Default to treating watch as a long-running operation.
31 | // Generic API servers have no inherent long-running subresources.
32 | // This is so watch requests are handled correctly in the audit log.
33 | LongRunningFunc: genericfilters.BasicLongRunningRequestCheck(
34 | sets.NewString("watch"), sets.NewString()),
35 | }
36 |
37 | // We do not support dynamic auditing, so leave nil
38 | if err := opts.ApplyTo(serverConfig); err != nil {
39 | return nil, err
40 | }
41 |
42 | serverConfig.EffectiveVersion = version.NewEffectiveVersion("1.0.31")
43 | completed := serverConfig.Complete(nil)
44 |
45 | return &Audit{
46 | opts: opts,
47 | serverConfig: &completed,
48 | }, nil
49 | }
50 |
51 | // Run will run the audit backend if configured.
52 | func (a *Audit) Run(stopCh <-chan struct{}) error {
53 | if a.serverConfig.AuditBackend != nil {
54 | if err := a.serverConfig.AuditBackend.Run(stopCh); err != nil {
55 | return fmt.Errorf("failed to run the audit backend: %s", err)
56 | }
57 | }
58 |
59 | return nil
60 | }
61 |
62 | // Shutdown will shutdown the audit backend if configured.
63 | func (a *Audit) Shutdown() error {
64 | if a.serverConfig.AuditBackend != nil {
65 | a.serverConfig.AuditBackend.Shutdown()
66 | }
67 |
68 | return nil
69 | }
70 |
71 | // WithRequest will wrap the given handler to inject the request information
72 | // into the context which is then used by the wrapped audit handler.
73 | func (a *Audit) WithRequest(handler http.Handler) http.Handler {
74 | handler = genericapifilters.WithAudit(handler, a.serverConfig.AuditBackend, a.serverConfig.AuditPolicyRuleEvaluator, a.serverConfig.LongRunningFunc)
75 | handler = genericapifilters.WithAuditInit(handler)
76 | return genericapifilters.WithRequestInfo(handler, a.serverConfig.RequestInfoResolver)
77 | }
78 |
79 | // WithUnauthorized will wrap the given handler to inject the request
80 | // information into the context which is then used by the wrapped audit
81 | // handler.
82 | func (a *Audit) WithUnauthorized(handler http.Handler) http.Handler {
83 | handler = genericapifilters.WithFailedAuthenticationAudit(handler, a.serverConfig.AuditBackend, a.serverConfig.AuditPolicyRuleEvaluator)
84 | return genericapifilters.WithRequestInfo(handler, a.serverConfig.RequestInfoResolver)
85 | }
86 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/lib/utils.libsonnet:
--------------------------------------------------------------------------------
1 | /*
2 | * Bitnami Kubernetes Production Runtime - A collection of services that makes it
3 | * easy to run production workloads in Kubernetes.
4 | *
5 | * Copyright 2018-2019 Bitnami
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | // Various opinionated helper functions, that might not be generally
21 | // useful in other deployments.
22 | local kube = import "kube.libsonnet";
23 |
24 | {
25 | path_join(prefix, suffix):: (
26 | if std.endsWith(prefix, "/") then prefix + suffix
27 | else prefix + "/" + suffix
28 | ),
29 |
30 | trimUrl(str):: (
31 | if std.endsWith(str, "/") then
32 | std.substr(str, 1, std.length(str) - 1)
33 | else
34 | str
35 | ),
36 |
37 | toJson(x):: (
38 | if std.type(x) == "string" then std.escapeStringJson(x)
39 | else std.toString(x)
40 | ),
41 |
42 | subdomain(fqdn):: (
43 | local parts = std.split(fqdn, ".");
44 | local tail = [parts[i] for i in std.range(1, std.length(parts)-1)];
45 | std.join(".", tail)
46 | ),
47 |
48 | TlsIngress(name):: kube.Ingress(name) {
49 | local this = self,
50 | metadata+: {
51 | annotations+: {
52 | "kubernetes.io/tls-acme": "true",
53 | "kubernetes.io/ingress.class": "nginx",
54 | },
55 | },
56 | spec+: {
57 | tls+: [{
58 | hosts: std.set([r.host for r in this.spec.rules]),
59 | secretName: this.metadata.name + "-tls",
60 | }],
61 | },
62 | },
63 |
64 | AuthIngress(name):: $.TlsIngress(name) {
65 | local this = self,
66 | host:: error "host is required",
67 | metadata+: {
68 | annotations+: {
69 | // NB: Our nginx-ingress no-auth-locations includes "/oauth2"
70 | "nginx.ingress.kubernetes.io/auth-signin": "https://%s/oauth2/start" % this.host,
71 | "nginx.ingress.kubernetes.io/auth-url": "https://%s/oauth2/auth" % this.host,
72 | "nginx.ingress.kubernetes.io/auth-response-headers": "X-Auth-Request-User, X-Auth-Request-Email",
73 | },
74 | },
75 |
76 | spec+: {
77 | rules+: [{
78 | // This is required until the oauth2-proxy domain whitelist
79 | // feature (or similar) is released. Until then, oauth2-proxy
80 | // *only supports* redirects to the same hostname (because we
81 | // don't want to allow "open redirects" to just anywhere).
82 | host: this.host,
83 | http: {
84 | paths: [{
85 | path: "/oauth2",
86 | backend: {
87 | // TODO: parameterise this based on oauth2 deployment
88 | serviceName: "oauth2-proxy",
89 | servicePort: 4180,
90 | },
91 | }],
92 | },
93 | }],
94 | },
95 | },
96 | }
97 |
--------------------------------------------------------------------------------
/pkg/proxy/context/context.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package context
3 |
4 | import (
5 | "net/http"
6 |
7 | "github.com/sebest/xff"
8 | "k8s.io/apiserver/pkg/authentication/user"
9 | "k8s.io/apiserver/pkg/endpoints/request"
10 | "k8s.io/client-go/transport"
11 |
12 | genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
13 | )
14 |
15 | type key int
16 |
17 | const (
18 | // noImpersonationKey is the context key for whether to use impersonation.
19 | noImpersonationKey key = iota
20 |
21 | // impersonationConfigKey is the context key for the impersonation config.
22 | impersonationConfigKey
23 |
24 | // bearerTokenKey is the context key for the bearer token.
25 | bearerTokenKey
26 |
27 | // bearerTokenKey is the context key for the client address.
28 | clientAddressKey
29 | )
30 |
31 | type ImpersonationRequest struct {
32 | ImpersonationConfig *transport.ImpersonationConfig
33 | InboundUser *user.Info
34 | ImpersonatedUser *user.Info
35 | }
36 |
37 | // WithNoImpersonation returns a copy of the request in which the noImpersonation context value is set.
38 | func WithNoImpersonation(req *http.Request) *http.Request {
39 | return req.WithContext(request.WithValue(req.Context(), noImpersonationKey, true))
40 | }
41 |
42 | // NoImpersonation returns whether the noImpersonation context key has been set
43 | func NoImpersonation(req *http.Request) bool {
44 | noImp, _ := req.Context().Value(noImpersonationKey).(bool)
45 | return noImp
46 | }
47 |
48 | // WithImpersonationConfig returns a copy of parent in which contains the impersonation configuration.
49 | func WithImpersonationConfig(req *http.Request, conf *ImpersonationRequest) *http.Request {
50 | ctxToReturn := request.WithValue(req.Context(), impersonationConfigKey, conf)
51 | if *conf.ImpersonatedUser != nil {
52 | ctxToReturn = genericapirequest.WithUser(ctxToReturn, *conf.ImpersonatedUser)
53 | }
54 | return req.WithContext(ctxToReturn)
55 | }
56 |
57 | // ImpersonationConfig returns the impersonation configuration held in the context if existing.
58 | func ImpersonationConfig(req *http.Request) *ImpersonationRequest {
59 | conf, _ := req.Context().Value(impersonationConfigKey).(*ImpersonationRequest)
60 | return conf
61 | }
62 |
63 | // WithBearerToken will add the bearer token to the request context from an http.Header to the request context.
64 | func WithBearerToken(req *http.Request, header http.Header) *http.Request {
65 | return req.WithContext(request.WithValue(req.Context(), bearerTokenKey, header.Get("Authorization")))
66 | }
67 |
68 | // BearerToken will return the bearer token stored in the request context.
69 | func BearerToken(req *http.Request) string {
70 | token, _ := req.Context().Value(bearerTokenKey).(string)
71 | return token
72 | }
73 |
74 | // RemoteAddress will attempt to return the source client address if available
75 | // in the request context. If it is not, it will be gathered from the request
76 | // and entered into the context.
77 | func RemoteAddr(req *http.Request) (*http.Request, string) {
78 | ctx := req.Context()
79 |
80 | clientAddress, ok := ctx.Value(clientAddressKey).(string)
81 | if !ok {
82 | clientAddress = xff.GetRemoteAddr(req)
83 | req = req.WithContext(request.WithValue(ctx, clientAddressKey, clientAddress))
84 | }
85 |
86 | return req, clientAddress
87 | }
88 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@jetstack.io or joshua.vanleeuwen@jetstack.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/externaldns.jsonnet:
--------------------------------------------------------------------------------
1 | /*
2 | * Bitnami Kubernetes Production Runtime - A collection of services that makes it
3 | * easy to run production workloads in Kubernetes.
4 | *
5 | * Copyright 2018-2019 Bitnami
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | local kube = import '../lib/kube.libsonnet';
21 | local EXTERNAL_DNS_IMAGE = (import 'images.json')['external-dns'];
22 |
23 | {
24 | p:: '',
25 | metadata:: {
26 | metadata+: {
27 | namespace: 'kubeprod',
28 | },
29 | },
30 |
31 | clusterRole: kube.ClusterRole($.p + 'external-dns') {
32 | rules: [
33 | {
34 | apiGroups: [''],
35 | resources: ['services'],
36 | verbs: ['get', 'watch', 'list'],
37 | },
38 | {
39 | apiGroups: [''],
40 | resources: ['pods'],
41 | verbs: ['get', 'watch', 'list'],
42 | },
43 | {
44 | apiGroups: ['extensions'],
45 | resources: ['ingresses'],
46 | verbs: ['get', 'watch', 'list'],
47 | },
48 | {
49 | apiGroups: [''],
50 | resources: ['nodes'],
51 | verbs: ['list'],
52 | },
53 | ],
54 | },
55 |
56 | clusterRoleBinding: kube.ClusterRoleBinding($.p + 'external-dns-viewer') {
57 | roleRef_: $.clusterRole,
58 | subjects_+: [$.sa],
59 | },
60 |
61 | sa: kube.ServiceAccount($.p + 'external-dns') + $.metadata {
62 | },
63 |
64 | deploy: kube.Deployment($.p + 'external-dns') + $.metadata {
65 | local this = self,
66 | ownerId:: error 'ownerId is required',
67 | domainFilter:: this.ownerId,
68 | spec+: {
69 | template+: {
70 | metadata+: {
71 | annotations+: {
72 | 'prometheus.io/scrape': 'true',
73 | 'prometheus.io/port': '7979',
74 | 'prometheus.io/path': '/metrics',
75 | },
76 | },
77 | spec+: {
78 | serviceAccountName: $.sa.metadata.name,
79 | containers_+: {
80 | edns: kube.Container('external-dns') {
81 | image: EXTERNAL_DNS_IMAGE,
82 | args_+: {
83 | sources_:: ['service', 'ingress'],
84 | 'txt-owner-id': this.ownerId,
85 | 'domain-filter': this.domainFilter,
86 | },
87 | args+: ['--source=%s' % s for s in self.args_.sources_],
88 | ports_+: {
89 | metrics: { containerPort: 7979 },
90 | },
91 | readinessProbe: {
92 | httpGet: { path: '/healthz', port: 'metrics' },
93 | },
94 | livenessProbe: self.readinessProbe,
95 | },
96 | },
97 | },
98 | },
99 | },
100 | },
101 | }
102 |
--------------------------------------------------------------------------------
/cmd/app/options/app.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package options
3 |
4 | import (
5 | "time"
6 |
7 | "github.com/spf13/pflag"
8 | cliflag "k8s.io/component-base/cli/flag"
9 |
10 | "github.com/jetstack/kube-oidc-proxy/pkg/util/flags"
11 | )
12 |
13 | type KubeOIDCProxyOptions struct {
14 | DisableImpersonation bool
15 | ReadinessProbePort int
16 |
17 | FlushInterval time.Duration
18 |
19 | ExtraHeaderOptions ExtraHeaderOptions
20 | TokenPassthrough TokenPassthroughOptions
21 | }
22 |
23 | type TokenPassthroughOptions struct {
24 | Audiences []string
25 | Enabled bool
26 | }
27 |
28 | type ExtraHeaderOptions struct {
29 | EnableClientIPExtraUserHeader bool
30 |
31 | ExtraUserHeaders map[string][]string
32 | }
33 |
34 | func NewKubeOIDCProxyOptions(nfs *cliflag.NamedFlagSets) *KubeOIDCProxyOptions {
35 | return new(KubeOIDCProxyOptions).AddFlags(nfs.FlagSet("Kube-OIDC-Proxy"))
36 | }
37 |
38 | func (k *KubeOIDCProxyOptions) AddFlags(fs *pflag.FlagSet) *KubeOIDCProxyOptions {
39 | fs.BoolVar(&k.DisableImpersonation, "disable-impersonation", k.DisableImpersonation,
40 | "(Alpha) Disable the impersonation of authenticated requests. All "+
41 | "authenticated requests will be forwarded as is.")
42 |
43 | fs.IntVarP(&k.ReadinessProbePort, "readiness-probe-port", "P", 8080,
44 | "Port to expose readiness probe.")
45 |
46 | fs.DurationVar(&k.FlushInterval, "flush-interval", time.Millisecond*50,
47 | "Specifies the interval to flush request bodies. If 0ms, "+
48 | "no periodic flushing is done. A negative value means to flush "+
49 | "immediately after each write. Streaming requests such as 'kubectl exec' "+
50 | "will ignore this option and flush immediately.")
51 |
52 | k.TokenPassthrough.AddFlags(fs)
53 | k.ExtraHeaderOptions.AddFlags(fs)
54 |
55 | return k
56 | }
57 |
58 | func (t *TokenPassthroughOptions) AddFlags(fs *pflag.FlagSet) {
59 | fs.StringSliceVar(&t.Audiences, "token-passthrough-audiences", t.Audiences, ""+
60 | "(Alpha) List of the identifiers that the resource server presented with the token "+
61 | "identifies as. The resource server will verify that non OIDC tokens are intended "+
62 | "for at least one of the audiences in this list. If no audiences are "+
63 | "provided, the audience will default to the audience of the Kubernetes "+
64 | "apiserver. Only used when --token-passthrough is also enabled.")
65 |
66 | fs.BoolVar(&t.Enabled, "token-passthrough", t.Enabled, ""+
67 | "(Alpha) Requests with Bearer tokens that fail OIDC validation are tried against "+
68 | "the API server using the Token Review endpoint. If successful, the request "+
69 | "is sent on as is, with no impersonation.")
70 | }
71 |
72 | func (e *ExtraHeaderOptions) AddFlags(fs *pflag.FlagSet) {
73 | fs.BoolVar(&e.EnableClientIPExtraUserHeader, "extra-user-header-client-ip",
74 | e.EnableClientIPExtraUserHeader, "(Alpha) If enabled, proxied requests will "+
75 | "include the extra user header 'Impersonate-Extra-Remote-Client-IP: "+
76 | "' where will contain the remote address of "+
77 | "the source of the request.")
78 |
79 | fs.Var(flags.NewStringToStringSliceValue(&e.ExtraUserHeaders), "extra-user-headers",
80 | "(Alpha) A list of key value pairs of extra user headers to pass with "+
81 | "proxied requests as part of the impersonated request. A single key can "+
82 | "hold multiple values.")
83 | }
84 |
--------------------------------------------------------------------------------
/demo/manifests/components/landingpage/amazon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/demo/manifests/vendor/kube-prod-runtime/components/kibana.jsonnet:
--------------------------------------------------------------------------------
1 | /*
2 | * Bitnami Kubernetes Production Runtime - A collection of services that makes it
3 | * easy to run production workloads in Kubernetes.
4 | *
5 | * Copyright 2018-2019 Bitnami
6 | *
7 | * Licensed under the Apache License, Version 2.0 (the "License");
8 | * you may not use this file except in compliance with the License.
9 | * You may obtain a copy of the License at
10 | *
11 | * http://www.apache.org/licenses/LICENSE-2.0
12 | *
13 | * Unless required by applicable law or agreed to in writing, software
14 | * distributed under the License is distributed on an "AS IS" BASIS,
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | * See the License for the specific language governing permissions and
17 | * limitations under the License.
18 | */
19 |
20 | local kube = import "../lib/kube.libsonnet";
21 | local kubecfg = import "kubecfg.libsonnet";
22 | local utils = import "../lib/utils.libsonnet";
23 |
24 | local KIBANA_IMAGE = (import "images.json")["kibana"];
25 |
26 | local strip_trailing_slash(s) = (
27 | if std.endsWith(s, "/") then
28 | strip_trailing_slash(std.substr(s, 0, std.length(s) - 1))
29 | else
30 | s
31 | );
32 |
33 | {
34 | p:: "",
35 | metadata:: {
36 | metadata+: {
37 | namespace: "kubeprod",
38 | },
39 | },
40 |
41 | es: error "elasticsearch is required",
42 |
43 | serviceAccount: kube.ServiceAccount($.p + "kibana") + $.metadata {
44 | },
45 |
46 | deploy: kube.Deployment($.p + "kibana") + $.metadata {
47 | spec+: {
48 | template+: {
49 | spec+: {
50 | securityContext: {
51 | fsGroup: 1001,
52 | },
53 | containers_+: {
54 | kibana: kube.Container("kibana") {
55 | image: KIBANA_IMAGE,
56 | securityContext: {
57 | runAsUser: 1001,
58 | },
59 | resources: {
60 | requests: {
61 | cpu: "10m",
62 | },
63 | limits: {
64 | cpu: "1000m", // initial startup requires lots of cpu
65 | },
66 | },
67 | env_+: {
68 | KIBANA_ELASTICSEARCH_URL: $.es.svc.host,
69 |
70 | local route = $.ingress.spec.rules[1].http.paths[0],
71 | // Make sure we got the correct route
72 | assert route.backend == $.svc.name_port,
73 | SERVER_BASEPATH: strip_trailing_slash(route.path),
74 | KIBANA_HOST: "0.0.0.0",
75 | XPACK_MONITORING_ENABLED: "false",
76 | XPACK_SECURITY_ENABLED: "false",
77 | },
78 | ports_+: {
79 | ui: { containerPort: 5601 },
80 | },
81 | },
82 | },
83 | },
84 | },
85 | },
86 | },
87 |
88 | svc: kube.Service($.p + "kibana-logging") + $.metadata {
89 | target_pod: $.deploy.spec.template,
90 | },
91 |
92 | ingress: utils.AuthIngress($.p + "kibana-logging") + $.metadata {
93 | local this = self,
94 | host:: error "host is required",
95 | spec+: {
96 | rules+: [
97 | {
98 | host: this.host,
99 | http: {
100 | paths: [
101 | { path: "/", backend: $.svc.name_port },
102 | ],
103 | },
104 | },
105 | ],
106 | },
107 | },
108 | }
109 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: build
2 | on:
3 | push:
4 | branches:
5 | - 'master'
6 |
7 | permissions:
8 | id-token: write
9 | packages: write
10 |
11 | jobs:
12 | docker:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | -
17 | name: Set up QEMU
18 | uses: docker/setup-qemu-action@v1
19 | -
20 | name: Set up Docker Buildx
21 | uses: docker/setup-buildx-action@v1
22 | - uses: actions/checkout@v1
23 |
24 | - name: Setup Go
25 | uses: actions/setup-go@v3
26 | with:
27 | go-version: "1.23.0"
28 |
29 | - name: Install Cosign
30 | uses: sigstore/cosign-installer@main
31 |
32 | - name: Update go deps
33 | run: go mod tidy
34 |
35 | - name: install go mock
36 | run: go install github.com/golang/mock/mockgen@v1.6.0
37 |
38 | - name: install go-junit
39 | run: go get -u github.com/jstemmer/go-junit-report
40 |
41 | - name: run tests
42 | run: make test
43 |
44 | - name: build executable
45 | run: make build; ls; ls bin
46 |
47 |
48 |
49 |
50 | -
51 | name: Login to DockerHub
52 | uses: docker/login-action@v1
53 | with:
54 | username: ${{ secrets.OU_REG_USER }}
55 | password: ${{ secrets.OU_REG_PASSWORD }}
56 |
57 | - name: Login to container Registry
58 | uses: docker/login-action@v2
59 | with:
60 | username: ${{ github.repository_owner }}
61 | password: ${{ secrets.GITHUB_TOKEN }}
62 | registry: ghcr.io
63 |
64 | - name: downcase REPO
65 | run: |
66 | echo "REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV}
67 |
68 | - name: generate tag
69 | run: |-
70 | export PROJ_VERSION="1.0.9"
71 | echo "Project Version: $PROJ_VERSION"
72 | echo "TAG=$PROJ_VERSION-$(echo $GITHUB_SHA | cut -c 1-6)" >> $GITHUB_ENV
73 | echo "SHORT_TAG=$PROJ_VERSION" >> $GITHUB_ENV
74 |
75 |
76 | -
77 | name: Build and push
78 | id: docker_build
79 | uses: docker/build-push-action@v2
80 | with:
81 | context: "."
82 | push: true
83 | platforms: linux/amd64,linux/arm64
84 | tags: |
85 | ${{ secrets.OU_CONTAINER_DEST }}:${{ env.TAG }}
86 | ${{ secrets.OU_CONTAINER_DEST }}:${{ env.SHORT_TAG }}
87 | ${{ secrets.OU_CONTAINER_DEST }}
88 | ghcr.io/${{ env.REPO }}:${{ env.TAG }}
89 | ghcr.io/${{ env.REPO }}:${{ env.SHORT_TAG }}
90 | ghcr.io/${{ env.REPO }}:latest
91 |
92 | - name: sign images
93 | run: |-
94 | cosign sign -y ghcr.io/${{ env.REPO }}:${{ env.TAG }}
95 |
96 | - uses: anchore/sbom-action@v0
97 | with:
98 | image: ghcr.io/${{ env.REPO }}:${{ env.TAG }}
99 | format: spdx
100 | output-file: /tmp/spdxg
101 |
102 | - name: attach sbom to images
103 | run: |-
104 | cosign attach sbom --sbom /tmp/spdxg ghcr.io/${{ env.REPO }}:${{ env.TAG }}
105 |
106 |
107 | GH_SBOM_SHA=$(cosign verify --certificate-oidc-issuer-regexp='.*' --certificate-identity-regexp='.*' ghcr.io/${{ env.REPO }}:${{ env.TAG }} 2>/dev/null | jq -r '.[0].critical.image["docker-manifest-digest"]' | cut -c 8-)
108 |
109 |
110 | echo "GH_SBOM_SHA: $GH_SBOM_SHA"
111 |
112 |
113 | cosign sign -y ghcr.io/${{ env.REPO }}:sha256-$GH_SBOM_SHA.sbom
114 |
115 |
--------------------------------------------------------------------------------
/demo/infrastructure/modules/amazon-cluster/cluster.tf:
--------------------------------------------------------------------------------
1 | variable "suffix" {}
2 | variable "cluster_version" {}
3 |
4 | data "aws_availability_zones" "available" {}
5 |
6 | locals {
7 | cluster_name = "cluster-${var.suffix}"
8 | }
9 |
10 | resource "aws_security_group" "worker_group_mgmt_one" {
11 | name_prefix = "worker_group_mgmt_one"
12 | vpc_id = module.vpc.vpc_id
13 |
14 | ingress {
15 | from_port = 22
16 | to_port = 22
17 | protocol = "tcp"
18 |
19 | cidr_blocks = [
20 | "10.0.0.0/8",
21 | ]
22 | }
23 | }
24 |
25 | resource "aws_security_group" "worker_group_mgmt_two" {
26 | name_prefix = "worker_group_mgmt_two"
27 | vpc_id = module.vpc.vpc_id
28 |
29 | ingress {
30 | from_port = 22
31 | to_port = 22
32 | protocol = "tcp"
33 |
34 | cidr_blocks = [
35 | "192.168.0.0/16",
36 | ]
37 | }
38 | }
39 |
40 | resource "aws_security_group" "all_worker_mgmt" {
41 | name_prefix = "all_worker_management"
42 | vpc_id = module.vpc.vpc_id
43 |
44 | ingress {
45 | from_port = 22
46 | to_port = 22
47 | protocol = "tcp"
48 |
49 | cidr_blocks = [
50 | "10.0.0.0/8",
51 | "172.16.0.0/12",
52 | "192.168.0.0/16",
53 | ]
54 | }
55 | }
56 |
57 | module "vpc" {
58 | source = "terraform-aws-modules/vpc/aws"
59 | version = "2.6.0"
60 |
61 | name = "test-vpc"
62 | cidr = "10.0.0.0/16"
63 | azs = "${data.aws_availability_zones.available.names}"
64 | private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
65 | public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]
66 | enable_nat_gateway = true
67 | single_nat_gateway = true
68 | enable_dns_hostnames = true
69 |
70 | tags = {
71 | "kubernetes.io/cluster/${local.cluster_name}" = "shared"
72 | }
73 |
74 | public_subnet_tags = {
75 | "kubernetes.io/cluster/${local.cluster_name}" = "shared"
76 | "kubernetes.io/role/elb" = "1"
77 | }
78 |
79 | private_subnet_tags = {
80 | "kubernetes.io/cluster/${local.cluster_name}" = "shared"
81 | "kubernetes.io/role/internal-elb" = "1"
82 | }
83 | }
84 |
85 | module "eks" {
86 | source = "terraform-aws-modules/eks/aws"
87 | cluster_name = "${local.cluster_name}"
88 | subnets = "${module.vpc.private_subnets}"
89 |
90 | tags = {
91 | Environment = "test"
92 | GithubRepo = "terraform-aws-eks"
93 | GithubOrg = "terraform-aws-modules"
94 | }
95 |
96 | vpc_id = "${module.vpc.vpc_id}"
97 |
98 | worker_groups = [
99 | {
100 | name = "worker-group-1"
101 | instance_type = "t2.small"
102 | additional_userdata = "echo foo bar"
103 | asg_desired_capacity = 2
104 | additional_security_group_ids = ["${aws_security_group.worker_group_mgmt_one.id}"]
105 | },
106 | {
107 | name = "worker-group-2"
108 | instance_type = "t2.medium"
109 | additional_userdata = "echo foo bar"
110 | additional_security_group_ids = ["${aws_security_group.worker_group_mgmt_two.id}"]
111 | asg_desired_capacity = 1
112 | },
113 | ]
114 |
115 | worker_additional_security_group_ids = ["${aws_security_group.all_worker_mgmt.id}"]
116 | }
117 |
118 | data "aws_eks_cluster" "cluster" {
119 | name = module.eks.cluster_id
120 | }
121 |
122 | data "aws_eks_cluster_auth" "cluster" {
123 | name = module.eks.cluster_id
124 | }
125 |
126 | provider "kubernetes" {
127 | host = data.aws_eks_cluster.cluster.endpoint
128 | cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
129 | token = data.aws_eks_cluster_auth.cluster.token
130 | load_config_file = false
131 | version = "~> 1.10"
132 | }
133 |
--------------------------------------------------------------------------------
/deploy/charts/kube-oidc-proxy/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for kube-oidc-proxy.
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 |
5 | replicaCount: 1
6 |
7 | image:
8 | repository: quay.io/jetstack/kube-oidc-proxy
9 | tag: v0.3.0
10 | pullPolicy: IfNotPresent
11 |
12 | imagePullSecrets: []
13 | nameOverride: ""
14 | fullnameOverride: ""
15 |
16 | service:
17 | type: ClusterIP
18 | port: 443
19 | annotations:
20 | # You can use this field to add annotations to the Service.
21 | # Define it in a key-value pairs. E.g.
22 | # service.beta.kubernetes.io/aws-load-balancer-internal: true
23 |
24 | loadBalancerIP: ""
25 | loadBalancerSourceRanges: []
26 |
27 | tls:
28 | # `secretName` must be a name of Secret of TLS type. If not provided a
29 | # self-signed certificate will get generated.
30 | secretName:
31 | # `certManager` if you have cert-manager in your cluster and dont want to manage manually
32 | certManager: false
33 | # `selfSigned` if you have cert-manager and perfer or not to use use default issuer or generate by using other issuer
34 | selfSigned: true
35 | # `issuerName` if `selfSigned` is false, you should add your own Issuer
36 | issuerName:
37 |
38 | # These values needs to be set in overrides in order to get kube-oidc-proxy
39 | # working.
40 | oidc:
41 | # A minimal configuration requires setting clientId, issuerUrl and usernameClaim
42 | # values.
43 | clientId: ""
44 | issuerUrl: ""
45 | usernameClaim: ""
46 |
47 | # PEM encoded value of CA cert that will verify TLS connection to
48 | # OIDC issuer URL. If not provided, default hosts root CA's will be used.
49 | caPEM:
50 |
51 | usernamePrefix:
52 | groupsClaim:
53 | groupsPrefix:
54 |
55 | signingAlgs:
56 | - RS256
57 | requiredClaims: {}
58 |
59 | # To enable token passthrough feature
60 | # https://github.com/jetstack/kube-oidc-proxy/blob/master/docs/tasks/token-passthrough.md
61 | tokenPassthrough:
62 | enabled: false
63 | audiences: []
64 |
65 | # To add extra impersonation headers
66 | # https://github.com/jetstack/kube-oidc-proxy/blob/master/docs/tasks/extra-impersonation-headers.md
67 | extraImpersonationHeaders:
68 | clientIP: false
69 | #headers: key1=foo,key2=bar,key1=bar
70 |
71 | extraArgs: {}
72 | #audit-log-path: /audit-log
73 | #audit-policy-file: /audit/audit.yaml
74 |
75 | extraVolumeMounts: {}
76 | #- name: audit
77 | # mountPath: /audit
78 | # readOnly: true
79 |
80 | extraVolumes: {}
81 | #- configMap:
82 | #defaultMode: 420
83 | #name: kube-oidc-proxy-policy
84 | #name: audit
85 |
86 | ingress:
87 | enabled: false
88 | annotations: {}
89 | # kubernetes.io/ingress.class: nginx
90 | # kubernetes.io/tls-acme: "true"
91 | hosts:
92 | - host: chart-example.local
93 | paths: []
94 |
95 | # ingressClassName: ''
96 |
97 | tls: []
98 | # - secretName: chart-example-tls
99 | # hosts:
100 | # - chart-example.local
101 |
102 | # Allows setting the Deployment update strategy
103 | #rollingUpdateStrategy:
104 | # type: RollingUpdate
105 | # rollingUpdate:
106 | # maxSurge: 34%
107 | # maxUnavailable: 33%
108 |
109 | # Enable Pod Disruption Budget
110 | podDisruptionBudget:
111 | enabled: false
112 | minAvailable: 1
113 |
114 | resources: {}
115 | # We usually recommend not to specify default resources and to leave this as a conscious
116 | # choice for the user. This also increases chances charts run on environments with little
117 | # resources, such as Minikube. If you do want to specify resources, uncomment the following
118 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
119 | # limits:
120 | # cpu: 100m
121 | # memory: 128Mi
122 | # requests:
123 | # cpu: 100m
124 | # memory: 128Mi
125 | #
126 |
127 | initContainers: []
128 |
129 | nodeSelector: {}
130 |
131 | tolerations: []
132 |
133 | affinity: {}
134 |
--------------------------------------------------------------------------------
/test/tools/issuer/pkg/issuer/issuer.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package issuer
3 |
4 | import (
5 | "crypto/rsa"
6 | "crypto/x509"
7 | "encoding/base64"
8 | "encoding/pem"
9 | "fmt"
10 | "io/ioutil"
11 | "net"
12 | "net/http"
13 |
14 | log "github.com/sirupsen/logrus"
15 | )
16 |
17 | type Issuer struct {
18 | issuerURL string
19 | keyFile, certFile string
20 |
21 | sk *rsa.PrivateKey
22 |
23 | stopCh <-chan struct{}
24 | }
25 |
26 | func New(issuerURL, keyFile, certFile string, stopCh <-chan struct{}) (*Issuer, error) {
27 | b, err := ioutil.ReadFile(keyFile)
28 | if err != nil {
29 | return nil, err
30 | }
31 |
32 | block, _ := pem.Decode(b)
33 | if block == nil {
34 | return nil,
35 | fmt.Errorf("failed to parse PEM block containing the key: %q", keyFile)
36 | }
37 |
38 | sk, err := x509.ParsePKCS1PrivateKey(block.Bytes)
39 | if err != nil {
40 | return nil, err
41 | }
42 |
43 | return &Issuer{
44 | keyFile: keyFile,
45 | certFile: certFile,
46 | issuerURL: issuerURL,
47 | sk: sk,
48 | stopCh: stopCh,
49 | }, nil
50 | }
51 |
52 | func (i *Issuer) Run(bindAddress, listenPort string) (<-chan struct{}, error) {
53 | serveAddr := fmt.Sprintf("%s:%s", bindAddress, listenPort)
54 |
55 | l, err := net.Listen("tcp", serveAddr)
56 | if err != nil {
57 | return nil, err
58 | }
59 |
60 | go func() {
61 | <-i.stopCh
62 | if l != nil {
63 | l.Close()
64 | }
65 | }()
66 |
67 | compCh := make(chan struct{})
68 | go func() {
69 | defer close(compCh)
70 |
71 | err := http.ServeTLS(l, i, i.certFile, i.keyFile)
72 | if err != nil {
73 | log.Errorf("stopped serving TLS (%s): %s", serveAddr, err)
74 | }
75 | }()
76 |
77 | log.Infof("mock issuer listening and serving on %s", serveAddr)
78 |
79 | return compCh, nil
80 | }
81 |
82 | func (i *Issuer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
83 | log.Infof("mock issuer received url %s", r.URL)
84 |
85 | rw.Header().Set("Content-Type", "application/json; charset=utf-8")
86 |
87 | switch r.URL.String() {
88 | case "/.well-known/openid-configuration":
89 | rw.WriteHeader(http.StatusOK)
90 |
91 | if _, err := rw.Write(i.wellKnownResponse()); err != nil {
92 | log.Errorf("failed to write openid-configuration response: %s", err)
93 | }
94 |
95 | case "/certs":
96 | rw.WriteHeader(http.StatusOK)
97 |
98 | certsDiscovery := i.certsDiscovery()
99 | if _, err := rw.Write(certsDiscovery); err != nil {
100 | log.Errorf("failed to write certificate discovery response: %s", err)
101 | }
102 |
103 | default:
104 | log.Errorf("unexpected URL request: %s", r.URL)
105 | rw.WriteHeader(http.StatusNotFound)
106 | if _, err := rw.Write([]byte("{}\n")); err != nil {
107 | log.Errorf("failed to write data to resposne: %s", err)
108 | }
109 | }
110 | }
111 |
112 | func (i *Issuer) wellKnownResponse() []byte {
113 | return []byte(fmt.Sprintf(`{
114 | "issuer": "%s",
115 | "jwks_uri": "%s/certs",
116 | "subject_types_supported": [
117 | "public"
118 | ],
119 | "id_token_signing_alg_values_supported": [
120 | "RS256"
121 | ],
122 | "scopes_supported": [
123 | "openid",
124 | "email"
125 | ],
126 | "token_endpoint_auth_methods_supported": [
127 | "client_secret_post",
128 | "client_secret_basic"
129 | ],
130 | "claims_supported": [
131 | "email",
132 | "e2e-username-claim",
133 | "e2e-groups-claim",
134 | "sub"
135 | ],
136 | "code_challenge_methods_supported": [
137 | "plain",
138 | "S256"
139 | ]
140 | }`, i.issuerURL, i.issuerURL))
141 | }
142 |
143 | func (i *Issuer) certsDiscovery() []byte {
144 | n := base64.RawURLEncoding.EncodeToString(i.sk.N.Bytes())
145 |
146 | return []byte(fmt.Sprintf(`{
147 | "keys": [
148 | {
149 | "kid": "0905d6f9cd9b0f1f852e8b207e8f673abca4bf75",
150 | "e": "AQAB",
151 | "kty": "RSA",
152 | "alg": "RS256",
153 | "n": "%s",
154 | "use": "sig"
155 | }
156 | ]
157 | }`, n))
158 | }
159 |
--------------------------------------------------------------------------------
/test/e2e/suite/cases/headers/headers.go:
--------------------------------------------------------------------------------
1 | // Copyright Jetstack Ltd. See LICENSE for details.
2 | package headers
3 |
4 | import (
5 | "fmt"
6 | "net/http"
7 | "strings"
8 | "time"
9 |
10 | . "github.com/onsi/ginkgo"
11 | . "github.com/onsi/gomega"
12 |
13 | "github.com/jetstack/kube-oidc-proxy/test/e2e/framework"
14 | testutil "github.com/jetstack/kube-oidc-proxy/test/util"
15 | )
16 |
17 | var _ = framework.CasesDescribe("Headers", func() {
18 | f := framework.NewDefaultFramework("headers")
19 |
20 | JustAfterEach(func() {
21 | By("Deleting fake API Server")
22 | err := f.Helper().DeleteFakeAPIServer(f.Namespace.Name)
23 | Expect(err).NotTo(HaveOccurred())
24 | })
25 |
26 | It("should not respond with any extra headers if none are set on the proxy", func() {
27 | extraOIDCVolumes, fakeAPIServerURL, err := f.Helper().DeployFakeAPIServer(f.Namespace.Name)
28 | Expect(err).NotTo(HaveOccurred())
29 |
30 | By("Redeploying proxy to send traffic to fake API server")
31 | f.DeployProxyWith(extraOIDCVolumes, fmt.Sprintf("--server=%s", fakeAPIServerURL), "--certificate-authority=/fake-apiserver/ca.pem")
32 |
33 | resp := sendRequestToProxy(f)
34 |
35 | By("Ensuring no extra headers sent by proxy")
36 | for k := range resp.Header {
37 | if strings.HasPrefix(strings.ToLower(k), "impersonate-extra-") {
38 | Expect(fmt.Errorf("expected no extra user headers, got=%+v", resp.Header)).NotTo(HaveOccurred())
39 | }
40 | }
41 | })
42 |
43 | It("should respond with remote address and custom extra headers when they are set", func() {
44 | By("Deploying fake API Server")
45 | extraOIDCVolumes, fakeAPIServerURL, err := f.Helper().DeployFakeAPIServer(f.Namespace.Name)
46 | Expect(err).NotTo(HaveOccurred())
47 |
48 | By("Redeploying proxy to send traffic to fake API server with extra headers set")
49 | f.DeployProxyWith(extraOIDCVolumes, fmt.Sprintf("--server=%s", fakeAPIServerURL), "--certificate-authority=/fake-apiserver/ca.pem",
50 | "--extra-user-header-client-ip", "--extra-user-headers=key1=foo,key2=foo,key1=bar")
51 |
52 | resp := sendRequestToProxy(f)
53 |
54 | By("Ensuring expected headers are present")
55 | cpyHeader := resp.Header.Clone()
56 |
57 | // Check expected headers
58 | for k, v := range map[string][]string{
59 | "Impersonate-Extra-Key1": []string{"foo", "bar"},
60 | "Impersonate-Extra-Key2": []string{"foo"},
61 | } {
62 | if !testutil.StringSlicesEqual(v, cpyHeader[k]) {
63 | Expect(fmt.Errorf("expected key %q to have value %q, but got headers: %v",
64 | k, v, cpyHeader)).NotTo(HaveOccurred())
65 | }
66 |
67 | cpyHeader.Del(k)
68 | }
69 |
70 | // Check expected client IP header
71 | // TODO: determine a reliable way to get ip to match
72 | headerIP, ok := cpyHeader["Impersonate-Extra-Remote-Client-Ip"]
73 | if !ok || len(headerIP) != 1 {
74 | Expect(fmt.Errorf("expected impersonate extra remote client ip user header, got=%v", resp.Header)).NotTo(HaveOccurred())
75 | }
76 |
77 | cpyHeader.Del("Impersonate-Extra-Remote-Client-Ip")
78 |
79 | By("Ensuring no extra user headers where added")
80 | for k := range cpyHeader {
81 | if strings.HasPrefix(strings.ToLower(k), "impersonate-extra-") {
82 | Expect(fmt.Errorf("expected no impersonate extra user headers, got=%+v", resp.Header)).NotTo(HaveOccurred())
83 | }
84 | }
85 | })
86 | })
87 |
88 | func sendRequestToProxy(f *framework.Framework) *http.Response {
89 | By("Building request to proxy")
90 | tokenPayload := f.Helper().NewTokenPayload(
91 | f.IssuerURL(), f.ClientID(), time.Now().Add(time.Minute))
92 |
93 | signedToken, err := f.Helper().SignToken(f.IssuerKeyBundle(), tokenPayload)
94 | Expect(err).NotTo(HaveOccurred())
95 |
96 | proxyConfig := f.NewProxyRestConfig()
97 | requester := f.Helper().NewRequester(proxyConfig.Transport, signedToken)
98 |
99 | By("Sending request to proxy")
100 | reqURL := fmt.Sprintf("%s/foo/bar", proxyConfig.Host)
101 | _, resp, err := requester.Get(reqURL)
102 | Expect(err).NotTo(HaveOccurred())
103 |
104 | return resp
105 | }
106 |
--------------------------------------------------------------------------------
/demo/manifests/components/gangway.jsonnet:
--------------------------------------------------------------------------------
1 | local kube = import '../vendor/kube-prod-runtime/lib/kube.libsonnet';
2 | local utils = import '../vendor/kube-prod-runtime/lib/utils.libsonnet';
3 |
4 | local GANGWAY_IMAGE = 'gcr.io/heptio-images/gangway:v3.0.0';
5 | local GANGWAY_PORT = 8080;
6 | local GANGWAY_CONFIG_VOLUME_PATH = '/etc/gangway';
7 | local GANGWAY_TLS_VOLUME_PATH = GANGWAY_CONFIG_VOLUME_PATH + '/tls';
8 |
9 | {
10 | p:: '',
11 |
12 | sessionSecurityKey:: error 'sessionSecurityKey is undefined',
13 |
14 | base_domain:: '.cluster.local',
15 |
16 | app:: 'gangway',
17 |
18 | name:: $.p + $.app,
19 |
20 | domain:: $.name + $.base_domain,
21 | gangway_url:: 'https://' + $.domain,
22 |
23 | namespace:: 'gangway',
24 |
25 | config_path:: GANGWAY_CONFIG_VOLUME_PATH,
26 |
27 | labels:: {
28 | metadata+: {
29 | labels+: {
30 | app: $.app,
31 | },
32 | },
33 | },
34 |
35 | metadata:: $.labels {
36 | metadata+: {
37 | namespace: $.namespace,
38 | },
39 | },
40 |
41 | config:: {
42 | usernameClaim: 'name',
43 | redirectURL: $.gangway_url + '/callback',
44 | clusterName: 'cluster-name',
45 | authorize_url: 'https://' + $.domain + '/auth',
46 | clientID: 'client-id',
47 | tokenURL: 'https://' + $.domain + '/token',
48 | scopes: [
49 | 'openid',
50 | 'email',
51 | 'profile',
52 | 'groups',
53 | 'offline_access',
54 | ],
55 | serveTLS: true,
56 | certFile: GANGWAY_TLS_VOLUME_PATH + '/tls.crt',
57 | keyFile: GANGWAY_TLS_VOLUME_PATH + '/tls.key',
58 | },
59 |
60 |
61 | configMap: kube.ConfigMap($.name) + $.metadata {
62 | data+: {
63 | 'gangway.yaml': std.manifestJsonEx($.config, ' '),
64 | },
65 | },
66 |
67 | secret: kube.Secret($.name) + $.metadata {
68 | data_+: {
69 | 'session-security-key': $.sessionSecurityKey,
70 | },
71 | },
72 |
73 | deployment: kube.Deployment($.name) + $.metadata {
74 | local this = self,
75 | spec+: {
76 | replicas: 1,
77 | template+: {
78 | metadata+: {
79 | annotations+: {
80 | 'config/hash': std.md5(std.escapeStringJson($.configMap)),
81 | 'secret/hash': std.md5(std.escapeStringJson($.secret)),
82 | },
83 | },
84 | spec+: {
85 | affinity: kube.PodZoneAntiAffinityAnnotation(this.spec.template),
86 | default_container: $.app,
87 | volumes_+: {
88 | config: kube.ConfigMapVolume($.configMap),
89 | tls: {
90 | secret: {
91 | secretName: $.name + '-tls',
92 | },
93 | },
94 | },
95 | containers_+: {
96 | gangway: kube.Container($.app) {
97 | image: GANGWAY_IMAGE,
98 | command: [$.app],
99 | args: [
100 | '-config',
101 | GANGWAY_CONFIG_VOLUME_PATH + '/gangway.yaml',
102 | ],
103 | ports_+: {
104 | http: { containerPort: GANGWAY_PORT },
105 | },
106 | env_+: {
107 | GANGWAY_SESSION_SECURITY_KEY: kube.SecretKeyRef($.secret, 'session-security-key'),
108 | GANGWAY_PORT: GANGWAY_PORT,
109 | },
110 | readinessProbe: {
111 | httpGet: { path: '/', port: GANGWAY_PORT, scheme: 'HTTPS' },
112 | periodSeconds: 10,
113 | },
114 | livenessProbe: {
115 | httpGet: { path: '/', port: GANGWAY_PORT, scheme: 'HTTPS' },
116 | initialDelaySeconds: 20,
117 | periodSeconds: 10,
118 | },
119 | volumeMounts_+: {
120 | config: { mountPath: GANGWAY_CONFIG_VOLUME_PATH },
121 | tls: { mountPath: GANGWAY_TLS_VOLUME_PATH },
122 | },
123 | },
124 | },
125 | },
126 | },
127 | },
128 | },
129 |
130 | svc: kube.Service($.name) + $.metadata {
131 | target_pod: $.deployment.spec.template,
132 | },
133 | }
134 |
--------------------------------------------------------------------------------