Pinniped Tutorial:
+Learn to use Pinniped for federated authentication to Kubernetes clusters
+diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 02b86132..851381ff 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,6 +3,7 @@ version: 2 updates: - package-ecosystem: "gomod" + open-pull-requests-limit: 100 directory: "/" schedule: interval: "daily" diff --git a/Dockerfile b/Dockerfile index 3d7f3f23..7f2b58d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # Copyright 2020-2022 the Pinniped contributors. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -FROM golang:1.17.6 as build-env +FROM golang:1.17.7 as build-env WORKDIR /work COPY . . diff --git a/ROADMAP.md b/ROADMAP.md index 6b7eb097..fcf193b8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -33,16 +33,16 @@ The following table includes the current roadmap for Pinniped. If you have any q -Last Updated: Sept 2021 +Last Updated: Jan 2022 |Theme|Description|Timeline| |--|--|--| -|Improving Security Posture|Supervisor token refresh fails when the upstream refresh token no longer works for OIDC |Jan 2022| -|Improving Security Posture|Supervisor token refresh fails when the upstream user is in an invalid state for LDAP/AD |Jan 2022| -|Improving Security Posture|Set stricter default TLS versions and Ciphers |Jan 2022| -|Improving Security Posture|Support FIPS compliant Boring crypto libraries |Feb 2022| +|Improving Security Posture|Support for refreshing LDAP/AD Group information |Feb 2022| +|Improving Documentation|Documentation updates for HowTo guides and Workspace ONE IDP |Feb/March 2022| +|Improving Security Posture|Support FIPS compliant Boring crypto libraries |Feb/March 2022| |Multiple IDP support|Support multiple IDPs configured on a single Supervisor|March/April 2022| |Improving Security Posture|TLS hardening |March/April 2022| |Improving Security Posture|Support Audit logging of security events related to Authentication |April/May 2022| +|Improving Usability|Support for integrating with UI/Dashboards |June/July 2022| |Improving Security Posture|mTLS for Supervisor sessions |Exploring/Ongoing| |Improving Security Posture|Key management/rotation for Pinniped components with minimal downtime |Exploring/Ongoing| |Improving Security Posture|Support for Session Logout |Exploring/Ongoing| diff --git a/go.mod b/go.mod index d0b4c019..e6a4d71b 100644 --- a/go.mod +++ b/go.mod @@ -54,8 +54,8 @@ require ( github.com/gorilla/websocket v1.4.2 github.com/joshlf/go-acl v0.0.0-20200411065538-eae00ae38531 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 - github.com/ory/fosite v0.42.0 - github.com/ory/x v0.0.337 + github.com/ory/fosite v0.42.1 + github.com/ory/x v0.0.344 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/pkg/errors v0.9.1 github.com/sclevine/agouti v3.0.0+incompatible @@ -63,30 +63,30 @@ require ( github.com/spf13/cobra v1.3.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 - github.com/tdewolff/minify/v2 v2.9.29 + github.com/tdewolff/minify/v2 v2.10.0 go.uber.org/atomic v1.9.0 - golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce - golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d + golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 + golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/text v0.3.7 gopkg.in/square/go-jose.v2 v2.6.0 - k8s.io/api v0.23.2 - k8s.io/apiextensions-apiserver v0.23.2 - k8s.io/apimachinery v0.23.2 - k8s.io/apiserver v0.23.2 - k8s.io/client-go v0.23.2 - k8s.io/component-base v0.23.2 + k8s.io/api v0.23.3 + k8s.io/apiextensions-apiserver v0.23.3 + k8s.io/apimachinery v0.23.3 + k8s.io/apiserver v0.23.3 + k8s.io/client-go v0.23.3 + k8s.io/component-base v0.23.3 k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 k8s.io/klog/v2 v2.40.1 - k8s.io/kube-aggregator v0.23.2 - k8s.io/utils v0.0.0-20211208161948-7d6a63dca704 + k8s.io/kube-aggregator v0.23.3 + k8s.io/utils v0.0.0-20220127004650-9b3446523e65 sigs.k8s.io/yaml v1.3.0 ) require ( - cloud.google.com/go/compute v1.1.0 // indirect + cloud.google.com/go/compute v1.2.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.24 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -114,9 +114,9 @@ require ( github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-openapi/swag v0.21.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.2.0 // indirect + github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -145,19 +145,19 @@ require ( github.com/pelletier/go-toml v1.9.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect - github.com/prometheus/client_golang v1.12.0 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/spf13/afero v1.8.0 // indirect + github.com/spf13/afero v1.8.1 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/tdewolff/parse/v2 v2.5.27 // indirect - go.etcd.io/etcd/api/v3 v3.5.1 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect - go.etcd.io/etcd/client/v3 v3.5.1 // indirect + go.etcd.io/etcd/api/v3 v3.5.2 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.2 // indirect + go.etcd.io/etcd/client/v3 v3.5.2 // indirect go.opentelemetry.io/contrib v0.20.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 // indirect @@ -170,22 +170,22 @@ require ( go.opentelemetry.io/otel/trace v1.2.0 // indirect go.opentelemetry.io/proto/otlp v0.10.0 // indirect go.uber.org/multierr v1.7.0 // indirect - go.uber.org/zap v1.20.0 // indirect + go.uber.org/zap v1.21.0 // indirect golang.org/x/mod v0.5.1 // indirect - golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect - golang.org/x/tools v0.1.8 // indirect + golang.org/x/tools v0.1.9 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect - google.golang.org/grpc v1.43.0 // indirect + google.golang.org/genproto v0.0.0-20220208230804-65c12eb4c068 // indirect + google.golang.org/grpc v1.44.0 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.66.3 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/kube-openapi v0.0.0-20220114203427-a0453230fd26 // indirect + k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.27 // indirect sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect diff --git a/go.sum b/go.sum index 5704a5eb..631f14b5 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,8 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.1.0 h1:pyPhehLfZ6pVzRgJmXGYvCY4K7WSWRhVw0AwhgVvS84= -cloud.google.com/go/compute v1.1.0/go.mod h1:2NIffxgWfORSI7EOYMFatGTfjMLnqrOKBEyYb6NoRgA= +cloud.google.com/go/compute v1.2.0 h1:EKki8sSdvDU0OO9mAXGwPXOTOgPz2l08R0/IutDH11I= +cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -262,7 +262,7 @@ github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1 github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= github.com/cockroachdb/cockroach-go v0.0.0-20200312223839-f565e4789405/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= -github.com/cockroachdb/cockroach-go/v2 v2.2.1/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI= +github.com/cockroachdb/cockroach-go/v2 v2.2.7/go.mod h1:q4ZRgO6CQpwNyEvEwSxwNrOSVchsmzrBnAv3HuZ3Abc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= @@ -593,8 +593,9 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= @@ -900,8 +901,9 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= +github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/gddo v0.0.0-20180828051604-96d2a289f41e/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= @@ -1507,8 +1509,8 @@ github.com/ory/dockertest/v3 v3.6.3/go.mod h1:EFLcVUOl8qCwp9NyDAcCDtq/QviLtYswW/ github.com/ory/dockertest/v3 v3.6.5/go.mod h1:iYKQSRlYrt/2s5fJWYdB98kCQG6g/LjBMvzEYii63vg= github.com/ory/dockertest/v3 v3.8.1/go.mod h1:wSRQ3wmkz+uSARYMk7kVJFDBGm8x5gSxIhI7NDc+BAQ= github.com/ory/fosite v0.29.0/go.mod h1:0atSZmXO7CAcs6NPMI/Qtot8tmZYj04Nddoold4S2h0= -github.com/ory/fosite v0.42.0 h1:ICAa2d7tR+kS/taYIyMzGKufGViC1bb/QAdOgLxFqlg= -github.com/ory/fosite v0.42.0/go.mod h1:qggrqm3ZWQF9i2f/d3RLH5mHHPtv44hsiltkVKLsCYo= +github.com/ory/fosite v0.42.1 h1:h2j+namO6bZIaD4WAFFHx+Cwfc8EDATE5JWQR9NMo6c= +github.com/ory/fosite v0.42.1/go.mod h1:qggrqm3ZWQF9i2f/d3RLH5mHHPtv44hsiltkVKLsCYo= github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90/go.mod h1:sxnvPCxChFuSmTJGj8FdMupeq1BezCiEpDjTUXQ4hf4= github.com/ory/go-acc v0.2.6/go.mod h1:4Kb/UnPcT8qRAk3IAxta+hvVapdxTLWtrr7bFLlEgpw= github.com/ory/go-acc v0.2.7 h1:VjSz+Xj3LoJRBmAXt9cxuL1lanO/Bvf9ajny5NvwbtM= @@ -1539,8 +1541,8 @@ github.com/ory/x v0.0.214/go.mod h1:aRl57gzyD4GF0HQCekovXhv0xTZgAgiht3o8eVhsm9Q= github.com/ory/x v0.0.250/go.mod h1:jUJaVptu+geeqlb9SyQCogTKj5ztSDIF6APkhbKtwLc= github.com/ory/x v0.0.272/go.mod h1:1TTPgJGQutrhI2OnwdrTIHE9ITSf4MpzXFzA/ncTGRc= github.com/ory/x v0.0.288/go.mod h1:APpShLyJcVzKw1kTgrHI+j/L9YM+8BRjHlcYObc7C1U= -github.com/ory/x v0.0.337 h1:YDlmEA43NMJJxezVVAQoqXyyYl/ZZSh9+mA8JSbOECw= -github.com/ory/x v0.0.337/go.mod h1:VxITg5o/DfPfom76ni5FfFzP66Z+kLvJ/OATJxuT42c= +github.com/ory/x v0.0.344 h1:ba74mT+d3UNNCdqSq5cTEHsjQc5UHoTjE9e873nNpRM= +github.com/ory/x v0.0.344/go.mod h1:Ddbu3ecSaNDgxdntdD1gDu3ALG5fWR5AwUB1ILeBUNE= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -1597,8 +1599,8 @@ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= -github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -1734,8 +1736,8 @@ github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52 github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60= -github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.8.1 h1:izYHOT71f9iZ7iq37Uqjael60/vYC6vMtzedudZ0zEk= +github.com/spf13/afero v1.8.1/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -1795,8 +1797,8 @@ github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/tdewolff/minify/v2 v2.9.29 h1:QMVJaCJzWL0mXS33cX792YD074xz4lOhkyBS8hAzYAY= -github.com/tdewolff/minify/v2 v2.9.29/go.mod h1:6XAjcHM46pFcRE0eztigFPm0Q+Cxsw8YhEWT+rDkcZM= +github.com/tdewolff/minify/v2 v2.10.0 h1:ovVAHUcjfGrBDf1EIvsodRUVJiZK/28mMose08B7k14= +github.com/tdewolff/minify/v2 v2.10.0/go.mod h1:6XAjcHM46pFcRE0eztigFPm0Q+Cxsw8YhEWT+rDkcZM= github.com/tdewolff/parse/v2 v2.5.27 h1:PL3LzzXaOpmdrknnOlIeO2muIBHAwiKp6TxN1RbU5gI= github.com/tdewolff/parse/v2 v2.5.27/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= @@ -1897,17 +1899,19 @@ go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mI go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489 h1:1JFLBqwIgdyHN1ZtgjTBwO+blA6gVOmZurpiMEsETKo= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.1 h1:v28cktvBq+7vGyJXF8G+rWJmj+1XUmMtqcLnH8hDocM= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.2 h1:tXok5yLlKyuQ/SXSjtqHc4uzNaMqZi2XsoSPr/LlJXI= +go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.1 h1:XIQcHCFSG53bJETYeRJtIxdLv2EWRGxcfzR8lSnTH4E= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.2 h1:4hzqQ6hIb3blLyQ8usCU4h3NghkqcsohEQ3o3VetYxE= +go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1 h1:vtxYCKWA9x31w0WJj7DdqsHFNjhkigdAnziDtkZb/l4= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/client/v3 v3.5.1 h1:oImGuV5LGKjCqXdjkMHCyWa5OO1gYKCnC/1sgdfj1Uk= -go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= +go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA= +go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o= go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw= @@ -1985,8 +1989,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc= -go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -2038,8 +2042,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI= -golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2 h1:XdAboW3BNMv9ocSCOk/u1MFioZGzCNkiJZ19v9Oe3Ig= +golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2169,8 +2173,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs= -golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2349,8 +2353,9 @@ golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2488,8 +2493,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= -golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2543,7 +2548,7 @@ google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUb google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.65.0/go.mod h1:ArYhxgGadlWmqO1IqVujw6Cs8IdD33bTmzKo2Sh+cbg= +google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2632,10 +2637,10 @@ google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q= -google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220208230804-65c12eb4c068 h1:pwzFiZfBTH/GjBWz1BcDwMBaHBo8mZvpLa7eBKJpFAk= +google.golang.org/genproto v0.0.0-20220208230804-65c12eb4c068/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2672,8 +2677,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= -google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -2721,8 +2726,8 @@ gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.66.3 h1:jRskFVxYaMGAMUbN0UZ7niA9gzL9B49DOqE78vg0k3w= -gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= @@ -2774,31 +2779,31 @@ howett.net/plist v0.0.0-20201203080718-1454fab16a06/go.mod h1:vMygbs4qMhSZSc4lCU k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.23.2 h1:62cpzreV3dCuj0hqPi8r4dyWh48ogMcyh+ga9jEGij4= -k8s.io/api v0.23.2/go.mod h1:sYuDb3flCtRPI8ghn6qFrcK5ZBu2mhbElxRE95qpwlI= -k8s.io/apiextensions-apiserver v0.23.2 h1:N6CIVAhmF0ahgFKUMDdV/AUyckhUb4nIyVPohPtdUPk= -k8s.io/apiextensions-apiserver v0.23.2/go.mod h1:9cs7avT6+GfzbB0pambTvH11wcaR85QQg4ovl9s15UU= +k8s.io/api v0.23.3 h1:KNrME8KHGr12Ozjf8ytOewKzZh6hl/hHUZeHddT3a38= +k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= +k8s.io/apiextensions-apiserver v0.23.3 h1:JvPJA7hSEAqMRteveq4aj9semilAZYcJv+9HHFWfUdM= +k8s.io/apiextensions-apiserver v0.23.3/go.mod h1:/ZpRXdgKZA6DvIVPEmXDCZJN53YIQEUDF+hrpIQJL38= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.23.2 h1:dBmjCOeYBdg2ibcQxMuUq+OopZ9fjfLIR5taP/XKeTs= -k8s.io/apimachinery v0.23.2/go.mod h1:zDqeV0AK62LbCI0CI7KbWCAYdLg+E+8UXJ0rIz5gmS8= +k8s.io/apimachinery v0.23.3 h1:7IW6jxNzrXTsP0c8yXz2E5Yx/WTzVPTsHIx/2Vm0cIk= +k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.23.2 h1:vGFCojjwSLyunapA7FWuzyekml/s0nAsoh4iBpzWzOs= -k8s.io/apiserver v0.23.2/go.mod h1:Kdt8gafkPev9Gfh+H6lCPbmRu42f7BfhOfHKKa3dtyU= +k8s.io/apiserver v0.23.3 h1:gWY1DmA0AdAGR/H+Q/1FtyGkFq8xqSaZOw7oLopmO8k= +k8s.io/apiserver v0.23.3/go.mod h1:3HhsTmC+Pn+Jctw+Ow0LHA4dQ4oXrQ4XJDzrVDG64T4= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.23.2 h1:BNbOcxa99jxHH8mM1cPKGIrrKRnCSAfAtyonYGsbFtE= -k8s.io/client-go v0.23.2/go.mod h1:k3YbsWg6GWdHF1THHTQP88X9RhB1DWPo3Dq7KfU/D1c= -k8s.io/code-generator v0.23.2/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/client-go v0.23.3 h1:23QYUmCQ/W6hW78xIwm3XqZrrKZM+LWDqW2zfo+szJs= +k8s.io/client-go v0.23.3/go.mod h1:47oMd+YvAOqZM7pcQ6neJtBiFH7alOyfunYN48VsmwE= +k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.23.2 h1:dAYmUhWIBWO762etTjBEEKtYYHi5CoQInSLtK6LM1Zs= -k8s.io/component-base v0.23.2/go.mod h1:wS9Z03MO3oJ0RU8bB/dbXTiluGju+SC/F5i660gxB8c= +k8s.io/component-base v0.23.3 h1:q+epprVdylgecijVGVdf4MbizEL2feW4ssd7cdo6LVY= +k8s.io/component-base v0.23.3/go.mod h1:1Smc4C60rWG7d3HjSYpIwEbySQ3YWg0uzH5a2AtaTLg= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= @@ -2813,18 +2818,18 @@ k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4= k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-aggregator v0.23.2 h1:6CoZZqNdFc9benrgSJJ0GQGgFtKjI0y3UwlBbioXtc8= -k8s.io/kube-aggregator v0.23.2/go.mod h1:hoxP4rZREnjCJmrb0pHFPqm7+pkxoFjh8IpXL7OBWRA= +k8s.io/kube-aggregator v0.23.3 h1:9IP+D+YzIbGor/TArN3pYf9Thj19wYhzLRGRrFaKFSs= +k8s.io/kube-aggregator v0.23.3/go.mod h1:pt5QJ3QaIdhZzNlUvN5wndbM0LNT4BvhszGkzy2QdFo= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kube-openapi v0.0.0-20220114203427-a0453230fd26 h1:2G24ndYyfk0l23ZrGutxb0s9TRe4m1ZjFlcu4cEU1zA= -k8s.io/kube-openapi v0.0.0-20220114203427-a0453230fd26/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf h1:M9XBsiMslw2lb2ZzglC0TOkBPK5NQi0/noUrdnoFwUg= +k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20211208161948-7d6a63dca704 h1:ZKMMxTvduyf5WUtREOqg5LiXaN1KO/+0oOQPRFrClpo= -k8s.io/utils v0.0.0-20211208161948-7d6a63dca704/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220127004650-9b3446523e65 h1:ONWS0Wgdg5wRiQIAui7L/023aC9+IxrIrydY7l8llsE= +k8s.io/utils v0.0.0-20220127004650-9b3446523e65/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= diff --git a/internal/oidc/provider/formposthtml/form_post.js b/internal/oidc/provider/formposthtml/form_post.js index 4c0eb7df..9e4b0203 100644 --- a/internal/oidc/provider/formposthtml/form_post.js +++ b/internal/oidc/provider/formposthtml/form_post.js @@ -1,4 +1,4 @@ -// Copyright 2021 the Pinniped contributors. All Rights Reserved. +// Copyright 2021-2022 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 window.onload = () => { @@ -44,11 +44,22 @@ window.onload = () => { responseParams['redirect_uri'].value, { method: 'POST', - mode: 'no-cors', + mode: 'no-cors', // in the future, we could change this to "cors" (see comment below) headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}, body: responseParams['encoded_params'].value, }) - .then(() => clearTimeout(timeout)) - .then(() => transitionToState('success')) + .then(response => { + clearTimeout(timeout); + // Requests made using "no-cors" mode will hide the real response.status by making it 0 + // and the real response.ok by making it false. + // If the real response was success, then we would like to show the success state. + // If the real response was an error, then we wish we could show the manual + // state, but we have no way to know that, as long as we are making "no-cors" requests. + // For now, show the success status for all responses. + // In the future, we could make this request in "cors" mode once old versions of our CLI + // which did not handle CORS are upgraded out by our users. That would allow us to use + // a conditional statement based on response.ok here to decide which state to transition into. + transitionToState('success'); + }) .catch(() => transitionToState('manual')); }; diff --git a/internal/oidc/provider/formposthtml/formposthtml_test.go b/internal/oidc/provider/formposthtml/formposthtml_test.go index b09c0d7b..94530a73 100644 --- a/internal/oidc/provider/formposthtml/formposthtml_test.go +++ b/internal/oidc/provider/formposthtml/formposthtml_test.go @@ -30,7 +30,7 @@ var (
- + @@ -61,7 +61,7 @@ var ( // It's okay if this changes in the future, but this gives us a chance to eyeball the formatting. // Our browser-based integration tests should find any incompatibilities. testExpectedCSP = `default-src 'none'; ` + - `script-src 'sha256-cjTdJmRvuz5EHNb/cw6pFk9iWyjegU9Ihx7Fb9tlqRg='; ` + + `script-src 'sha256-1LS3gM7wTGc0dYXZiqW6HK1LHk74YSG8GsJBC/j1/i8='; ` + `style-src 'sha256-CtfkX7m8x2UdGYvGgDq+6b6yIAQsASW9pbQK+sG8fNA='; ` + `img-src data:; ` + `connect-src *; ` + diff --git a/pkg/oidcclient/login.go b/pkg/oidcclient/login.go index d9364689..afda042a 100644 --- a/pkg/oidcclient/login.go +++ b/pkg/oidcclient/login.go @@ -834,21 +834,68 @@ func (h *handlerState) handleAuthCodeCallback(w http.ResponseWriter, r *http.Req }() var params url.Values - if h.useFormPost { - // Return HTTP 405 for anything that's not a POST. - if r.Method != http.MethodPost { - return httperr.Newf(http.StatusMethodNotAllowed, "wanted POST") + if h.useFormPost { // nolint:nestif + // Return HTTP 405 for anything that's not a POST or an OPTIONS request. + if r.Method != http.MethodPost && r.Method != http.MethodOptions { + h.logger.V(debugLogLevel).Info("Pinniped: Got unexpected request on callback listener", "method", r.Method) + w.WriteHeader(http.StatusMethodNotAllowed) + return nil // keep listening for more requests } - // Parse and pull the response parameters from a application/x-www-form-urlencoded request body. + // For POST and OPTIONS requests, calculate the allowed origin for CORS. + issuerURL, parseErr := url.Parse(h.issuer) + if parseErr != nil { + return httperr.Wrap(http.StatusInternalServerError, "invalid issuer url", parseErr) + } + allowOrigin := issuerURL.Scheme + "://" + issuerURL.Host + + if r.Method == http.MethodOptions { + // Google Chrome decided that it should do CORS preflight checks for this Javascript form submission POST request. + // See https://developer.chrome.com/blog/private-network-access-preflight/ + origin := r.Header.Get("Origin") + if origin == "" { + // The CORS preflight request should have an origin. + h.logger.V(debugLogLevel).Info("Pinniped: Got OPTIONS request without origin header") + w.WriteHeader(http.StatusBadRequest) + return nil // keep listening for more requests + } + h.logger.V(debugLogLevel).Info("Pinniped: Got CORS preflight request from browser", "origin", origin) + // To tell the browser that it is okay to make the real POST request, return the following response. + w.Header().Set("Access-Control-Allow-Origin", allowOrigin) + w.Header().Set("Vary", "*") // supposed to use Vary when Access-Control-Allow-Origin is a specific host + w.Header().Set("Access-Control-Allow-Credentials", "false") + w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Private-Network", "true") + // If the browser would like to send some headers on the real request, allow them. Chrome doesn't + // currently send this header at the moment. This is in case some browser in the future decides to + // request to be allowed to send specific headers by using Access-Control-Request-Headers. + requestedHeaders := r.Header.Get("Access-Control-Request-Headers") + if requestedHeaders != "" { + w.Header().Set("Access-Control-Allow-Headers", requestedHeaders) + } + w.WriteHeader(http.StatusNoContent) + return nil // keep listening for more requests + } // Otherwise, this is a POST request... + + // Parse and pull the response parameters from an application/x-www-form-urlencoded request body. if err := r.ParseForm(); err != nil { return httperr.Wrap(http.StatusBadRequest, "invalid form", err) } params = r.Form + + // Allow CORS requests for POST so in the future our Javascript code can be updated to use the fetch API's + // mode "cors", and still be compatible with older CLI versions starting with those that have this code + // for CORS headers. Updating to use CORS would allow our Javascript code (form_post.js) to see the true + // http response status from this endpoint. Note that the POST response does not need to set as many CORS + // headers as the OPTIONS preflight response. + w.Header().Set("Access-Control-Allow-Origin", allowOrigin) + w.Header().Set("Vary", "*") // supposed to use Vary when Access-Control-Allow-Origin is a specific host } else { // Return HTTP 405 for anything that's not a GET. if r.Method != http.MethodGet { - return httperr.Newf(http.StatusMethodNotAllowed, "wanted GET") + h.logger.V(debugLogLevel).Info("Pinniped: Got unexpected request on callback listener", "method", r.Method) + w.WriteHeader(http.StatusMethodNotAllowed) + return nil // keep listening for more requests } // Pull response parameters from the URL query string. diff --git a/pkg/oidcclient/login_test.go b/pkg/oidcclient/login_test.go index 8ee920d7..fe6a8ce5 100644 --- a/pkg/oidcclient/login_test.go +++ b/pkg/oidcclient/login_test.go @@ -1825,6 +1825,8 @@ func TestHandlePasteCallback(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + h := &handlerState{ callbacks: make(chan callbackResult, 1), state: state.State("test-state"), @@ -1866,62 +1868,161 @@ func TestHandleAuthCodeCallback(t *testing.T) { } } tests := []struct { - name string - method string - query string - body []byte - contentType string - opt func(t *testing.T) Option - wantErr string - wantHTTPStatus int + name string + method string + query string + body []byte + headers http.Header + opt func(t *testing.T) Option + + wantErr string + wantHTTPStatus int + wantNoCallbacks bool + wantHeaders http.Header }{ { - name: "wrong method", - method: "POST", - query: "", - wantErr: "wanted GET", - wantHTTPStatus: http.StatusMethodNotAllowed, + name: "wrong method returns an error but keeps listening", + method: http.MethodPost, + query: "", + wantNoCallbacks: true, + wantHeaders: map[string][]string{}, + wantHTTPStatus: http.StatusMethodNotAllowed, }, { - name: "wrong method for form_post", - method: "GET", - query: "", - opt: withFormPostMode, - wantErr: "wanted POST", - wantHTTPStatus: http.StatusMethodNotAllowed, + name: "wrong method for form_post returns an error but keeps listening", + method: http.MethodGet, + query: "", + opt: withFormPostMode, + wantNoCallbacks: true, + wantHeaders: map[string][]string{}, + wantHTTPStatus: http.StatusMethodNotAllowed, }, { name: "invalid form for form_post", - method: "POST", + method: http.MethodPost, query: "", - contentType: "application/x-www-form-urlencoded", + headers: map[string][]string{"Content-Type": {"application/x-www-form-urlencoded"}}, body: []byte(`%`), opt: withFormPostMode, wantErr: `invalid form: invalid URL escape "%"`, + wantHeaders: map[string][]string{}, wantHTTPStatus: http.StatusBadRequest, }, { name: "invalid state", query: "state=invalid", wantErr: "missing or invalid state parameter", + wantHeaders: map[string][]string{}, wantHTTPStatus: http.StatusForbidden, }, { name: "error code from provider", query: "state=test-state&error=some_error", wantErr: `login failed with code "some_error"`, + wantHeaders: map[string][]string{}, wantHTTPStatus: http.StatusBadRequest, }, { name: "error code with a description from provider", query: "state=test-state&error=some_error&error_description=optional%20error%20description", wantErr: `login failed with code "some_error": optional error description`, + wantHeaders: map[string][]string{}, wantHTTPStatus: http.StatusBadRequest, }, + { + name: "in form post mode, invalid issuer url config during CORS preflight request returns an error", + method: http.MethodOptions, + query: "", + headers: map[string][]string{"Origin": {"https://some-origin.com"}}, + wantErr: `invalid issuer url: parse "://bad-url": missing protocol scheme`, + wantHeaders: map[string][]string{}, + wantHTTPStatus: http.StatusInternalServerError, + opt: func(t *testing.T) Option { + return func(h *handlerState) error { + h.useFormPost = true + h.issuer = "://bad-url" + return nil + } + }, + }, + { + name: "in form post mode, invalid issuer url config during POST request returns an error", + method: http.MethodPost, + query: "", + headers: map[string][]string{"Origin": {"https://some-origin.com"}}, + wantErr: `invalid issuer url: parse "://bad-url": missing protocol scheme`, + wantHeaders: map[string][]string{}, + wantHTTPStatus: http.StatusInternalServerError, + opt: func(t *testing.T) Option { + return func(h *handlerState) error { + h.useFormPost = true + h.issuer = "://bad-url" + return nil + } + }, + }, + { + name: "in form post mode, options request is missing origin header results in 400 and keeps listener running", + method: http.MethodOptions, + query: "", + opt: withFormPostMode, + wantNoCallbacks: true, + wantHeaders: map[string][]string{}, + wantHTTPStatus: http.StatusBadRequest, + }, + { + name: "in form post mode, valid CORS request responds with 402 and CORS headers and keeps listener running", + method: http.MethodOptions, + query: "", + headers: map[string][]string{"Origin": {"https://some-origin.com"}}, + wantNoCallbacks: true, + wantHTTPStatus: http.StatusNoContent, + wantHeaders: map[string][]string{ + "Access-Control-Allow-Credentials": {"false"}, + "Access-Control-Allow-Methods": {"POST, OPTIONS"}, + "Access-Control-Allow-Origin": {"https://valid-issuer.com"}, + "Vary": {"*"}, + "Access-Control-Allow-Private-Network": {"true"}, + }, + opt: func(t *testing.T) Option { + return func(h *handlerState) error { + h.useFormPost = true + h.issuer = "https://valid-issuer.com/with/some/path" + return nil + } + }, + }, + { + name: "in form post mode, valid CORS request with Access-Control-Request-Headers responds with 402 and CORS headers including Access-Control-Allow-Headers and keeps listener running", + method: http.MethodOptions, + query: "", + headers: map[string][]string{ + "Origin": {"https://some-origin.com"}, + "Access-Control-Request-Headers": {"header1, header2, header3"}, + }, + wantNoCallbacks: true, + wantHTTPStatus: http.StatusNoContent, + wantHeaders: map[string][]string{ + "Access-Control-Allow-Credentials": {"false"}, + "Access-Control-Allow-Methods": {"POST, OPTIONS"}, + "Access-Control-Allow-Origin": {"https://valid-issuer.com"}, + "Vary": {"*"}, + "Access-Control-Allow-Private-Network": {"true"}, + "Access-Control-Allow-Headers": {"header1, header2, header3"}, + }, + opt: func(t *testing.T) Option { + return func(h *handlerState) error { + h.useFormPost = true + h.issuer = "https://valid-issuer.com/with/some/path" + return nil + } + }, + }, { name: "invalid code", query: "state=test-state&code=invalid", wantErr: "could not complete code exchange: some exchange error", + wantHeaders: map[string][]string{}, wantHTTPStatus: http.StatusBadRequest, opt: func(t *testing.T) Option { return func(h *handlerState) error { @@ -1938,8 +2039,10 @@ func TestHandleAuthCodeCallback(t *testing.T) { }, }, { - name: "valid", - query: "state=test-state&code=valid", + name: "valid", + query: "state=test-state&code=valid", + wantHTTPStatus: http.StatusOK, + wantHeaders: map[string][]string{"Content-Type": {"text/plain; charset=utf-8"}}, opt: func(t *testing.T) Option { return func(h *handlerState) error { h.oauth2Config = &oauth2.Config{RedirectURL: testRedirectURI} @@ -1955,10 +2058,45 @@ func TestHandleAuthCodeCallback(t *testing.T) { }, }, { - name: "valid form_post", - method: http.MethodPost, - contentType: "application/x-www-form-urlencoded", - body: []byte(`state=test-state&code=valid`), + name: "valid form_post", + method: http.MethodPost, + headers: map[string][]string{"Content-Type": {"application/x-www-form-urlencoded"}}, + body: []byte(`state=test-state&code=valid`), + wantHeaders: map[string][]string{ + "Access-Control-Allow-Origin": {"https://valid-issuer.com"}, + "Vary": {"*"}, + "Content-Type": {"text/plain; charset=utf-8"}, + }, + wantHTTPStatus: http.StatusOK, + opt: func(t *testing.T) Option { + return func(h *handlerState) error { + h.useFormPost = true + h.oauth2Config = &oauth2.Config{RedirectURL: testRedirectURI} + h.getProvider = func(_ *oauth2.Config, _ *oidc.Provider, _ *http.Client) provider.UpstreamOIDCIdentityProviderI { + mock := mockUpstream(t) + mock.EXPECT(). + ExchangeAuthcodeAndValidateTokens(gomock.Any(), "valid", pkce.Code("test-pkce"), nonce.Nonce("test-nonce"), testRedirectURI). + Return(&oidctypes.Token{IDToken: &oidctypes.IDToken{Token: "test-id-token"}}, nil) + return mock + } + return nil + } + }, + }, + { + name: "valid form_post made with the same origin headers that would be used by a Javascript fetch client using mode=cors", + method: http.MethodPost, + headers: map[string][]string{ + "Content-Type": {"application/x-www-form-urlencoded"}, + "Origin": {"https://some-origin.com"}, + }, + body: []byte(`state=test-state&code=valid`), + wantHeaders: map[string][]string{ + "Access-Control-Allow-Origin": {"https://valid-issuer.com"}, + "Vary": {"*"}, + "Content-Type": {"text/plain; charset=utf-8"}, + }, + wantHTTPStatus: http.StatusOK, opt: func(t *testing.T) Option { return func(h *handlerState) error { h.useFormPost = true @@ -1978,11 +2116,15 @@ func TestHandleAuthCodeCallback(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() + h := &handlerState{ callbacks: make(chan callbackResult, 1), state: state.State("test-state"), pkce: pkce.Code("test-pkce"), nonce: nonce.Nonce("test-nonce"), + logger: testlogger.New(t).Logger, + issuer: "https://valid-issuer.com/with/some/path", } if tt.opt != nil { require.NoError(t, tt.opt(t)(h)) @@ -1998,8 +2140,8 @@ func TestHandleAuthCodeCallback(t *testing.T) { if tt.method != "" { req.Method = tt.method } - if tt.contentType != "" { - req.Header.Set("Content-Type", tt.contentType) + if tt.headers != nil { + req.Header = tt.headers } err = h.handleAuthCodeCallback(resp, req) @@ -2012,11 +2154,19 @@ func TestHandleAuthCodeCallback(t *testing.T) { } } else { require.NoError(t, err) + require.Equal(t, tt.wantHTTPStatus, resp.Code) } + if tt.wantHeaders != nil { + require.Equal(t, tt.wantHeaders, resp.Header()) + } + + gotCallback := false select { case <-time.After(1 * time.Second): - require.Fail(t, "timed out waiting to receive from callbacks channel") + if !tt.wantNoCallbacks { + require.Fail(t, "timed out waiting to receive from callbacks channel") + } case result := <-h.callbacks: if tt.wantErr != "" { require.EqualError(t, result.err, tt.wantErr) @@ -2025,7 +2175,9 @@ func TestHandleAuthCodeCallback(t *testing.T) { require.NoError(t, result.err) require.NotNil(t, result.token) require.Equal(t, result.token.IDToken.Token, "test-id-token") + gotCallback = true } + require.Equal(t, tt.wantNoCallbacks, !gotCallback) }) } } diff --git a/site/config.yaml b/site/config.yaml index 5a6ee14d..ca08620c 100644 --- a/site/config.yaml +++ b/site/config.yaml @@ -7,7 +7,7 @@ params: github_url: "https://github.com/vmware-tanzu/pinniped" slack_url: "https://kubernetes.slack.com/messages/pinniped" community_url: "https://go.pinniped.dev/community" - latest_version: v0.13.0 + latest_version: v0.14.0 pygmentsCodefences: true pygmentsStyle: "pygments" markup: diff --git a/site/content/docs/_index.md b/site/content/docs/_index.md index e297ecf3..d78b655e 100644 --- a/site/content/docs/_index.md +++ b/site/content/docs/_index.md @@ -13,6 +13,10 @@ As a Kubernetes cluster administrator or user, you can learn how Pinniped works, Have a question, comment, or idea? Please reach out via [GitHub Discussions](https://github.com/vmware-tanzu/pinniped/discussions) or [join the Pinniped community meetings]({{< ref "/community" >}}). +## New to Pinniped? + +- ⚠️ **Start here:** [Learn to use Pinniped for federated authentication to Kubernetes clusters]({{< ref "tutorials/concierge-and-supervisor-demo" >}}) + ## Background {{< docsmenu "background" >}} diff --git a/site/content/docs/background/architecture.md b/site/content/docs/background/architecture.md index 6991e2bb..e1bf72af 100644 --- a/site/content/docs/background/architecture.md +++ b/site/content/docs/background/architecture.md @@ -45,6 +45,8 @@ Pinniped supports the following IDPs. 1. Any [LDAP](https://ldap.com) identity provider. +1. Any Active Directory identity provider (via LDAP). + The [`idp.supervisor.pinniped.dev`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-idp-supervisor-pinniped-dev-v1alpha1) API group contains the Kubernetes custom resources that configure the Pinniped diff --git a/site/content/docs/howto/configure-concierge-supervisor-jwt.md b/site/content/docs/howto/configure-concierge-supervisor-jwt.md index c7ee2418..bc15b3a9 100644 --- a/site/content/docs/howto/configure-concierge-supervisor-jwt.md +++ b/site/content/docs/howto/configure-concierge-supervisor-jwt.md @@ -26,7 +26,7 @@ If you would rather not use the Supervisor, you may want to [configure the Conci This how-to guide assumes that you have already [installed the Pinniped Supervisor]({{< ref "install-supervisor" >}}) with working ingress, and that you have [configured a FederationDomain to issue tokens for your downstream clusters]({{< ref "configure-supervisor" >}}). -It also assumes that you have configured an `OIDCIdentityProvider` or an `LDAPIdentityProvider` for the Supervisor as the source of your user's identities. +It also assumes that you have configured an `OIDCIdentityProvider`, `LDAPIdentityProvider`, or `ActiveDirectoryIdentityProvider` for the Supervisor as the source of your user's identities. Various examples of configuring these resources can be found in these guides. It also assumes that you have already [installed the Pinniped Concierge]({{< ref "install-concierge" >}}) diff --git a/site/content/docs/howto/login.md b/site/content/docs/howto/login.md index edc3329a..45cfb3ad 100644 --- a/site/content/docs/howto/login.md +++ b/site/content/docs/howto/login.md @@ -18,7 +18,7 @@ This how-to guide assumes that you have already configured the following Pinnipe then you have already: 1. [Installed the Pinniped Supervisor]({{< ref "install-supervisor" >}}) with working ingress. 1. [Configured a FederationDomain to issue tokens for your downstream clusters]({{< ref "configure-supervisor" >}}). - 1. Configured an `OIDCIdentityProvider` or an `LDAPIdentityProvider` for the Supervisor as the source of your user's identities. + 1. Configured an `OIDCIdentityProvider`, `LDAPIdentityProvider`, or `ActiveDirectoryIdentityProvider` for the Supervisor as the source of your user's identities. Various examples of configuring these resources can be found in these guides. 1. In each cluster for which you would like to use Pinniped for authentication, you have [installed the Concierge]({{< ref "install-concierge" >}}). 1. In each cluster's Concierge, you have configured an authenticator. For example, if you are using the Pinniped Supervisor, @@ -35,8 +35,13 @@ You should have also already [installed the `pinniped` command-line]({{< ref "in Although you can choose to use Pinniped without using the Pinniped Supervisor, there are several key advantages of choosing to use the Pinniped Supervisor to manage identity across fleets of Kubernetes clusters. +1. The Supervisor makes it easy to **bring your own OIDC, LDAP, or Active Directory identity provider to act as the source of user identities**. + It also allows you to configure how identities and group memberships in the identity provider map to identities + and group memberships in the Kubernetes clusters. + 1. A generated kubeconfig for a cluster will be specific for that cluster, however **it will not contain any specific user identity or credentials. - This kubeconfig file can be safely shared with all cluster users.** When the user runs `kubectl` commands using this kubeconfig, they will be interactively prompted to log in using their own unique identity from the OIDC or LDAP identity provider configured in the Supervisor. + This kubeconfig file can be safely shared with all cluster users.** When the user runs `kubectl` commands using this kubeconfig, + they will be interactively prompted to log in using their own unique identity from the identity provider configured in the Supervisor. 1. The Supervisor will provide a federated identity across all clusters that use the same `FederationDomain`. The user will be **prompted by `kubectl` to interactively authenticate once per day**, and then will be able to use all clusters @@ -44,10 +49,6 @@ Although you can choose to use Pinniped without using the Pinniped Supervisor, t This federated identity is secure because behind the scenes the Supervisor is issuing very short-lived credentials that are uniquely scoped to each cluster. -1. The Supervisor makes it easy to **bring your own OIDC or LDAP identity provider to act as the source of user identities**. - It also allows you to configure how identities and group memberships in the OIDC or LDAP identity provider map to identities - and group memberships in the Kubernetes clusters. - ## Generate a Pinniped-compatible kubeconfig file You will need to generate a Pinniped-compatible kubeconfig file for each cluster in which you have installed the Concierge. diff --git a/site/content/docs/tutorials/concierge-and-supervisor-demo.md b/site/content/docs/tutorials/concierge-and-supervisor-demo.md index a8b4def5..394d3166 100644 --- a/site/content/docs/tutorials/concierge-and-supervisor-demo.md +++ b/site/content/docs/tutorials/concierge-and-supervisor-demo.md @@ -1,5 +1,5 @@ --- -title: Learn to use the Pinniped Supervisor alongside the Concierge +title: Learn to use Pinniped for federated authentication to Kubernetes clusters description: See how the Pinniped Supervisor streamlines login to multiple Kubernetes clusters. cascade: layout: docs @@ -7,233 +7,761 @@ menu: docs: name: Concierge with Supervisor parent: tutorials + weight: 1 --- -## Prerequisites +## Why Pinniped? -1. A Kubernetes cluster of a type supported by Pinniped Concierge as described in [architecture](/docs/background/architecture). +There are many benefits to using the Pinniped Supervisor, Concierge, and CLI components together +to provide Kubernetes authentication. - Don't have a cluster handy? Consider using [kind](https://kind.sigs.k8s.io/) on your local machine. - See below for an example of using kind. +- It's easy to **bring your own OIDC, LDAP, or Active Directory identity provider** to act as the source of user identities. + A user's identity in the external identity provider becomes their identity in Kubernetes. + All other aspects of Kubernetes that are sensitive to identity, such as authorization policies and audit logging, are then + based on the user identities from your identity provider. -1. A Kubernetes cluster of a type supported by Pinniped Supervisor (this can be the same cluster as the first, or different). +- You can **bring identities from your own identity provider into many types of Kubernetes clusters in a consistent way**. + This includes clusters from various vendors run on-prem, and clusters provided as a cloud service by various popular cloud companies. -1. A kubeconfig that has administrator-like privileges on each cluster. +- Kubeconfig files **will not contain any specific user identity or credentials, so they can be safely shared**. -1. An external OIDC identity provider to use as the source of identity for Pinniped. +- Deep integration with `kubectl` means that when a user runs `kubectl` commands, + they will be **interactively prompted to log in using their own unique identity** from your identity provider. -## Overview +- Users will be prompted by `kubectl` to interactively **authenticate only once per day**, and then will be able to + use multiple clusters for the rest of the day without being asked to authenticate again. -Installing and trying Pinniped on any cluster consists of the following general steps. See the next section below -for a more specific example, including the commands to use for that case. +- All credentials are short-lived, and refreshed often. Additionally, **frequent checks are made against your identity provider + to ensure that the user should continue to have access to the Kubernetes clusters**. For example, within minutes + of locking an Active Directory account, that user will lose access to Kubernetes clusters, even if they were + already logged in. -1. [Install the Supervisor]({{< ref "../howto/install-supervisor" >}}). -1. Create a - [`FederationDomain`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomain) - via the installed Pinniped Supervisor. -1. Create an - [`OIDCIdentityProvider`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-supervisor-idp-v1alpha1-oidcidentityprovider) - via the installed Pinniped Supervisor. -1. Install the Pinniped Concierge. See [deploy/concierge/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/concierge/README.md). -1. Create a - [`JWTAuthenticator`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticator) - via the installed Pinniped Concierge. -1. [Install the Pinniped command-line tool]({{< ref "../howto/install-cli" >}}). -1. Generate a kubeconfig using the Pinniped command-line tool. Run `pinniped get kubeconfig --help` for more information. -1. Run `kubectl` commands using the generated kubeconfig. The Pinniped Supervisor and Concierge are automatically used for authentication during those commands. +- A **user can safely be granted high levels of authorization on a cluster**, if needed. + Even if they abuse their privilege by capturing the credentials sent by other users to the cluster, + they will not be able to use the captured credentials to access other clusters, because all credentials + sent to clusters are uniquely scoped to each individual cluster. -## Example of deploying on multiple kind clusters +- Pinniped will not interfere with a cluster's original vendor-specific authentication system. + The **original admin-level kubeconfig from a cluster can be privately kept by the cluster's creator** for + bootstrapping and break-glass access purposes. -[kind](https://kind.sigs.k8s.io) is a tool for creating and managing Kubernetes clusters on your local machine -which uses Docker containers as the cluster's nodes. This is a convenient way to try out Pinniped on local -non-production clusters. +- Pinniped is **open source** and will never be tied to any one vendor's authentication system. + As Pinniped improves in the future, all your Kubernetes clusters can benefit, regardless of which vendor provided the clusters. + The code is available on GitHub for any expert to audit, and for any community member to contribute. -The following steps deploy the latest release of Pinniped on kind. They deploy the Pinniped -Supervisor on one cluster, and the Pinniped Concierge on another cluster. A multi-cluster deployment -strategy is typical for Pinniped. The Pinniped Concierge uses a -[`JWTAuthenticator`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticator) -to authenticate federated identities from the Supervisor. +## What this tutorial will show -1. Install the tools required for the following steps. +This tutorial will show: +- A detailed example of how to install and configure a Supervisor with ingress, DNS, TLS, and an external identity provider +- How to install the Concierge onto multiple workload clusters and configure them all to trust identities from the Supervisor +- How an admin can create and distribute kubeconfig files for the workload clusters +- How a developer or devops user can authenticate with kubectl using their identity from the external identity provider, + and how they can securely access all workload clusters for the rest of the day without needing to authenticate again - - [Install kind](https://kind.sigs.k8s.io/docs/user/quick-start/), if not already installed. For example, `brew install kind` on macOS. +## Tutorial background - - kind depends on Docker. If not already installed, [install Docker](https://docs.docker.com/get-docker/), for example `brew cask install docker` on macOS. +This tutorial is intended to be a step-by-step example of installing and configuring the Pinniped components +to provide a multi-cluster federated authentication solution. It will show every +command needed to replicate the same setup to allow the reader to follow the same steps themselves. - - This demo requires `kubectl`, which comes with Docker, or can be [installed separately](https://kubernetes.io/docs/tasks/tools/install-kubectl/). +A single Pinniped Supervisor can provide authentication for any number of Kubernetes clusters. In a typical deployment: - - This demo requires `openssl`, which is installed on macOS by default, or can be [installed separately](https://www.openssl.org/). +- A single Supervisor is deployed on a special cluster where app developers and devops users have no access. + App developers and devops users should have no access at least to the resources in the Supervisor's namespace, + but usually have no access to the whole cluster. For this tutorial, let's call this cluster the *"supervisor cluster"*. +- App developers and devops users can then use their identities provided by the Supervisor to log in to many + clusters where they can manage their apps. For this tutorial, let's call these clusters the *"workload clusters"*. + The Pinniped Concierge component is installed into each workload cluster and is configured to trust the single Supervisor. + The Concierge acts as an in-cluster agent to provide authentication services. -1. Create a new Kubernetes cluster for the Pinniped Supervisor using `kind create cluster --name pinniped-supervisor`. +There are many ways to install and configure Pinniped. To make the steps of this tutorial as specific as possible, we +had to make some choices. The choices made for this tutorial were: -1. Create a new Kubernetes cluster for the Pinniped Concierge using `kind create cluster --name pinniped-concierge`. +- The Pinniped Supervisor can draw user identities from OIDC identity providers, Active Directory providers (via LDAP), + and generic LDAP providers. In this tutorial we will use Okta as an OIDC identity provider. + Okta offers a free developer account, so any reader should be able to sign up for an Okta + account if they would like to try these steps themselves. +- The Pinniped Supervisor can be installed on any type of Kubernetes cluster. In this tutorial we will + demonstrate the installation process for GKE because any reader should be able to sign up for a Google Cloud + account if they would like to try these steps themselves. We will use separate supervisor and workload clusters. +- The Pinniped Supervisor needs working ingress. There are many ways to configure ingress for apps running on + Kubernetes clusters, as described in the [howto guide for installing the Supervisor]({{< ref "../howto/install-supervisor" >}}). + For this tutorial we will use a LoadBalancer Service with a public IP address. This is a simple setup which + allows us to terminate TLS inside the Supervisor app, keeping the connection secure all the way into + the Supervisor app's pods. A corporate installation of the Supervisor might keep it behind the corporate firewall, + but for this tutorial a public IP also allows your desktop (and anyone on the internet) to access the Supervisor's endpoints. + The HTTPS endpoints of a properly configured Supervisor are generally safe to expose publicly, as long as you are not concerned + with denial of service attacks (or have some external protection against such attacks). +- Although it is possible to configure the Supervisor's FederationDomain to use an IP address, it is better to + use a DNS name. There are many ways to manage DNS. For this tutorial, we will use Google Cloud's + [Cloud DNS](https://cert-manager.io/docs/) service to register a new hostname for the Supervisor + app's load balancer's public IP address. We won't describe how to prepare Cloud DNS to manage DNS for + the parent domain in this tutorial. This typically involves setting up Cloud DNS's servers as the list of DNS servers + for your domain within your domain registrar. We'll assume that this has already been done. +- For web-based login flows as used by OIDC identity providers, the Pinniped Supervisor needs TLS certificates + that are trusted by the end users' web browsers. There are many ways to create TLS certificates. + There are also several ways to configure the TLS certificates on the Supervisor, as described in the + [docs for configuring the Supervisor]({{< ref "../howto/configure-supervisor" >}}). + For this tutorial we will use [Let's Encrypt](https://letsencrypt.org) with [cert-manager](https://cert-manager.io/docs/), + because any reader could use these services if they would like to try these steps themselves. +- The Pinniped Concierge can be installed in many types of Kubernetes clusters, as described in + [supported Kubernetes clusters]({{< ref "../reference/supported-clusters" >}}). In this tutorial we will + use GKE clusters as our workload clusters, for the same reasons that we are using GKE for the supervisor cluster. + It is worth noting that a Supervisor running on GKE can provide authentication for workload clusters of any supported + Kubernetes type, not only for GKE workload clusters. +- GKE and Google Cloud DNS can be managed in the Google Cloud Console web UI, or via the gcloud CLI. For this tutorial, + we will use the [gcloud CLI](https://cloud.google.com/sdk/docs/quickstart) so we can be as specific as possible. + However, the same steps could be performed via the UI instead. + This tutorial assumes that you have already authenticated with the gcloud CLI as a user who has permission to + run all the gcloud commands used below. +- Pinniped provides authentication, not authorization. Inside Kubernetes, a user authenticated via Pinniped will have a username + and may also have a list of group names. These usernames and group names can be used to create authorization policies using any + Kubernetes authorization system, usually using [Kubernetes RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac). -1. Deploy the Pinniped Supervisor with a valid serving certificate and network path. See - [deploy/supervisor/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/supervisor/README.md). +The details of the steps shown in this tutorial would be different if any of the above choices were made differently, +however the general concepts at each step would still apply. - For purposes of this demo, the following issuer is used. This issuer is specific to DNS and - TLS infrastructure set up for this demo: +## Ready? Let's go! - ```sh - issuer=https://my-supervisor.demo.pinniped.dev - ``` +### Install the Pinniped CLI - This demo uses a `Secret` named `my-federation-domain-tls` to provide the serving certificate for - the - [`FederationDomain`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomain). The - serving certificate `Secret` must be of type `kubernetes.io/tls`. +If you have not already done so, [install the Pinniped command-line tool]({{< ref "../howto/install-cli" >}}). - The CA bundle for this serving - certificate is assumed to be written, base64-encoded, to a file named - `/tmp/pinniped-supervisor-ca-bundle-base64-encoded.pem`. +On macOS or Linux, you can do this using Homebrew: -1. Create a - [`FederationDomain`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomain) - object to configure the Pinniped Supervisor to issue federated identities. +```sh +brew install vmware-tanzu/pinniped/pinniped-cli +``` - ```sh - cat <Community Meetings and Demos
-We have a YouTube playlist for our Pinniped community meetings and demos, check it out here.
+We have a YouTube playlist for our Pinniped community meetings and demos, check it out here.