mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-08 23:23:39 +00:00
Merge pull request #1958 from vmware-tanzu/jtc/merge-main-at-6b3f175-into-github
Merge main at `6b3f175` into `github_identity_provider`
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
ARG BUILD_IMAGE=golang:1.22.3@sha256:b1e05e2c918f52c59d39ce7d5844f73b2f4511f7734add8bb98c9ecdd4443365
|
||||
ARG BUILD_IMAGE=golang:1.22.3@sha256:ab48cd7b8e2cffb6fa1199de232f61c76d3c33dc158be8a998c5407a8e5eb583
|
||||
ARG BASE_IMAGE=gcr.io/distroless/static:nonroot@sha256:e9ac71e2b8e279a8372741b7a0293afda17650d926900233ec3a7b2b7c22a246
|
||||
|
||||
# Prepare to cross-compile by always running the build stage in the build platform, not the target platform.
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
| Maintainer | GitHub ID | Affiliation |
|
||||
|-----------------|-----------------------------------------------------------|------------------------------------------|
|
||||
| Ben Petersen | [benjaminapetersen](https://github.com/benjaminapetersen) | [VMware](https://www.github.com/vmware/) |
|
||||
| Ryan Richard | [cfryanr](https://github.com/cfryanr) | [VMware](https://www.github.com/vmware/) |
|
||||
| Joshua T. Casey | [joshuatcasey](https://github.com/joshuatcasey) | [VMware](https://www.github.com/vmware/) |
|
||||
|
||||
## Emeritus Maintainers
|
||||
|
||||
| Maintainer | GitHub ID |
|
||||
|-------------------|---------------------------------------------------------|
|
||||
| Andrew Keesler | [ankeesler](https://github.com/ankeesler) |
|
||||
| Anjali Telang | [anjaltelang](https://github.com/anjaltelang) |
|
||||
| Margo Crawford | [margocrawf](https://github.com/margocrawf) |
|
||||
| Matt Moyer | [mattmoyer](https://github.com/mattmoyer) |
|
||||
| Mo Khan | [enj](https://github.com/enj) |
|
||||
| Pablo Schuhmacher | [pabloschuhmacher](https://github.com/pabloschuhmacher) |
|
||||
| Maintainer | GitHub ID |
|
||||
|-------------------|-----------------------------------------------------------|
|
||||
| Andrew Keesler | [ankeesler](https://github.com/ankeesler) |
|
||||
| Anjali Telang | [anjaltelang](https://github.com/anjaltelang) |
|
||||
| Ben Petersen | [benjaminapetersen](https://github.com/benjaminapetersen) |
|
||||
| Margo Crawford | [margocrawf](https://github.com/margocrawf) |
|
||||
| Matt Moyer | [mattmoyer](https://github.com/mattmoyer) |
|
||||
| Mo Khan | [enj](https://github.com/enj) |
|
||||
| Pablo Schuhmacher | [pabloschuhmacher](https://github.com/pabloschuhmacher) |
|
||||
|
||||
@@ -47,4 +47,4 @@ Please follow the procedure described in [SECURITY.md](SECURITY.md).
|
||||
|
||||
Pinniped is open source and licensed under Apache License Version 2.0. See [LICENSE](LICENSE).
|
||||
|
||||
Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
|
||||
17
ROADMAP.md
17
ROADMAP.md
@@ -5,8 +5,6 @@
|
||||
This document provides a high-level overview of the next big features the maintainers are planning to work on. This
|
||||
should serve as a reference point for Pinniped users and contributors to understand where the project is heading, and
|
||||
help determine if a contribution could be conflicting with a longer term plan.
|
||||
The [Pinniped project backlog](https://github.com/orgs/vmware-tanzu/projects/43/) is prioritized based on this roadmap,
|
||||
and it provides a more granular view of what the maintainers are working on a day-to-day basis.
|
||||
|
||||
### How to help
|
||||
|
||||
@@ -23,19 +21,6 @@ a [proposal](https://github.com/vmware-tanzu/pinniped/tree/main/proposals) in ou
|
||||
For smaller enhancements, you can open an issue to track that initiative or feature request.
|
||||
We work with and rely on community feedback to focus our efforts to improve Pinniped and maintain a healthy roadmap.
|
||||
|
||||
### Current Roadmap
|
||||
|
||||
The following table includes the current roadmap for Pinniped. Please take the timelines and dates as proposals and
|
||||
goals. Priorities and requirements change based on community feedback, roadblocks encountered, community contributions,
|
||||
Priorities and requirements change based on community feedback, roadblocks encountered, community contributions,
|
||||
etc. If you depend on a specific item, we encourage you to reach out for updated status information, or help us deliver
|
||||
that feature by [contributing](https://github.com/vmware-tanzu/pinniped/blob/main/CONTRIBUTING.md) to Pinniped.
|
||||
|
||||
Last Updated: Sept 2022
|
||||
|Theme|Description|Timeline|
|
||||
|--|--|--|
|
||||
|Improving Usability|Dynamic Oauth Client Support for integrating with UI/Dashboards |Sept/Oct 2022|
|
||||
|Improving Usability|Support for custom claim mappings in OIDCIdentityProvider |Q4 2022|
|
||||
|Improving Usability|Support for Multiple Identity Providers |Q4 2022|
|
||||
|Improving Security Posture|Support Audit logging of security events related to Authentication |Q4 2022|
|
||||
|Improving Security Posture|Session Management |2022/2023|
|
||||
|Improving Security Posture|Secrets Rotation and Management |2022/2023|
|
||||
|
||||
9
SCOPE.md
9
SCOPE.md
@@ -21,12 +21,3 @@ The following items are out of scope for the Pinniped project.
|
||||
- Standalone identity provider for general use.
|
||||
- Machine-to-machine (service) identity.
|
||||
- Running outside of Kubernetes.
|
||||
|
||||
## Roadmap
|
||||
|
||||
See our [open milestones][milestones] and the [`priority/backlog` label][backlog] for an idea about what's next on our roadmap.
|
||||
|
||||
For more details on proposing features and bugs, check out our [contributing](./CONTRIBUTING.md) doc.
|
||||
|
||||
[milestones]: https://github.com/vmware-tanzu/pinniped/milestones
|
||||
[backlog]: https://github.com/vmware-tanzu/pinniped/labels/priority%2Fbacklog
|
||||
22
go.mod
22
go.mod
@@ -32,7 +32,7 @@ replace github.com/coreos/go-oidc/v3 => github.com/coreos/go-oidc/v3 v3.9.0
|
||||
|
||||
require (
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
||||
github.com/chromedp/cdproto v0.0.0-20240501202034-ef67d660e9fd
|
||||
github.com/chromedp/cdproto v0.0.0-20240512230644-b3296df1660c
|
||||
github.com/chromedp/chromedp v0.9.5
|
||||
github.com/coreos/go-oidc/v3 v3.10.0
|
||||
github.com/coreos/go-semver v0.3.1
|
||||
@@ -60,7 +60,7 @@ require (
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tdewolff/minify/v2 v2.20.20
|
||||
github.com/tdewolff/minify/v2 v2.20.22
|
||||
go.uber.org/mock v0.4.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
@@ -69,15 +69,15 @@ require (
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/term v0.20.0
|
||||
golang.org/x/text v0.15.0
|
||||
k8s.io/api v0.30.0
|
||||
k8s.io/apiextensions-apiserver v0.30.0
|
||||
k8s.io/apimachinery v0.30.0
|
||||
k8s.io/apiserver v0.30.0
|
||||
k8s.io/client-go v0.30.0
|
||||
k8s.io/component-base v0.30.0
|
||||
k8s.io/api v0.30.1
|
||||
k8s.io/apiextensions-apiserver v0.30.1
|
||||
k8s.io/apimachinery v0.30.1
|
||||
k8s.io/apiserver v0.30.1
|
||||
k8s.io/client-go v0.30.1
|
||||
k8s.io/component-base v0.30.1
|
||||
k8s.io/gengo v0.0.0-20240404160639-a0386bf69313
|
||||
k8s.io/klog/v2 v2.120.1
|
||||
k8s.io/kube-aggregator v0.30.0
|
||||
k8s.io/kube-aggregator v0.30.1
|
||||
k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f
|
||||
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
@@ -160,7 +160,7 @@ require (
|
||||
github.com/spf13/viper v1.16.0 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.7.13 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.7.14 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.10 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.10 // indirect
|
||||
@@ -197,7 +197,7 @@ require (
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/kms v0.30.0 // indirect
|
||||
k8s.io/kms v0.30.1 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
|
||||
44
go.sum
44
go.sum
@@ -69,8 +69,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/cdproto v0.0.0-20240501202034-ef67d660e9fd h1:5/HXKq8EaAWVmnl6Hnyl4SVq7FF5990DBW6AuTrWtVw=
|
||||
github.com/chromedp/cdproto v0.0.0-20240501202034-ef67d660e9fd/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/cdproto v0.0.0-20240512230644-b3296df1660c h1:IrHOOrmmJtVS1Z7tW+z71ZHTe6nYUqARg19Od8ECsJg=
|
||||
github.com/chromedp/cdproto v0.0.0-20240512230644-b3296df1660c/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg=
|
||||
github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
|
||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||
@@ -567,10 +567,10 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tdewolff/minify/v2 v2.20.20 h1:vhULb+VsW2twkplgsawAoUY957efb+EdiZ7zu5fUhhk=
|
||||
github.com/tdewolff/minify/v2 v2.20.20/go.mod h1:GYaLXFpIIwsX99apQHXfGdISUdlA98wmaoWxjT9C37k=
|
||||
github.com/tdewolff/parse/v2 v2.7.13 h1:iSiwOUkCYLNfapHoqdLcqZVgvQ0jrsao8YYKP/UJYTI=
|
||||
github.com/tdewolff/parse/v2 v2.7.13/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/minify/v2 v2.20.22 h1:N3SK8oIuAEF911qP0LR21dtD8ppXulEfy1HkIL/hZH8=
|
||||
github.com/tdewolff/minify/v2 v2.20.22/go.mod h1:1TJni7+mATKu24cBQQpgwakrYRD27uC1/rdJOgdv8ns=
|
||||
github.com/tdewolff/parse/v2 v2.7.14 h1:100KJ+QAO3PpMb3uUjzEU/NpmCdbBYz6KPmCIAfWpR8=
|
||||
github.com/tdewolff/parse/v2 v2.7.14/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
@@ -1084,27 +1084,27 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA=
|
||||
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
|
||||
k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs=
|
||||
k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y=
|
||||
k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA=
|
||||
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
|
||||
k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M=
|
||||
k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY=
|
||||
k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ=
|
||||
k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY=
|
||||
k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o=
|
||||
k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ=
|
||||
k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY=
|
||||
k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM=
|
||||
k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws=
|
||||
k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4=
|
||||
k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U=
|
||||
k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
|
||||
k8s.io/apiserver v0.30.1 h1:BEWEe8bzS12nMtDKXzCF5Q5ovp6LjjYkSp8qOPk8LZ8=
|
||||
k8s.io/apiserver v0.30.1/go.mod h1:i87ZnQ+/PGAmSbD/iEKM68bm1D5reX8fO4Ito4B01mo=
|
||||
k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q=
|
||||
k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc=
|
||||
k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ=
|
||||
k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI=
|
||||
k8s.io/gengo v0.0.0-20240404160639-a0386bf69313 h1:wBIDZID8ju9pwOiLlV22YYKjFGtiNSWgHf5CnKLRUuM=
|
||||
k8s.io/gengo v0.0.0-20240404160639-a0386bf69313/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
|
||||
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
|
||||
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kms v0.30.0 h1:ZlnD/ei5lpvUlPw6eLfVvH7d8i9qZ6HwUQgydNVks8g=
|
||||
k8s.io/kms v0.30.0/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4=
|
||||
k8s.io/kube-aggregator v0.30.0 h1:+Opc0lmhRmHbNM4m3mLSsUFmK/ikMapO9rvGirX5CEM=
|
||||
k8s.io/kube-aggregator v0.30.0/go.mod h1:KbZZkSSjYE6vkB2TSuZ9GBjU3ucgL7YxT8yX8wll0iQ=
|
||||
k8s.io/kms v0.30.1 h1:gEIbEeCbFiaN2tNfp/EUhFdGr5/CSj8Eyq6Mkr7cCiY=
|
||||
k8s.io/kms v0.30.1/go.mod h1:GrMurD0qk3G4yNgGcsCEmepqf9KyyIrTXYR2lyUOJC4=
|
||||
k8s.io/kube-aggregator v0.30.1 h1:ymR2BsxDacTKwzKTuNhGZttuk009c+oZbSeD+IPX5q4=
|
||||
k8s.io/kube-aggregator v0.30.1/go.mod h1:SFbqWsM6ea8dHd3mPLsZFzJHbjBOS5ykIgJh4znZ5iQ=
|
||||
k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f h1:0LQagt0gDpKqvIkAMPaRGcXawNMouPECM1+F9BVxEaM=
|
||||
k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f/go.mod h1:S9tOR0FxgyusSNR+MboCuiDpVWkAifZvaYI1Q2ubgro=
|
||||
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak=
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# See https://go.googlesource.com/go/+/dev.boringcrypto/README.boringcrypto.md
|
||||
# and https://kupczynski.info/posts/fips-golang/ for details.
|
||||
|
||||
ARG BUILD_IMAGE=golang:1.22.3@sha256:b1e05e2c918f52c59d39ce7d5844f73b2f4511f7734add8bb98c9ecdd4443365
|
||||
ARG BUILD_IMAGE=golang:1.22.3@sha256:ab48cd7b8e2cffb6fa1199de232f61c76d3c33dc158be8a998c5407a8e5eb583
|
||||
ARG BASE_IMAGE=gcr.io/distroless/static:nonroot@sha256:e9ac71e2b8e279a8372741b7a0293afda17650d926900233ec3a7b2b7c22a246
|
||||
|
||||
# This is not currently using --platform to prepare to cross-compile because we use gcc below to build
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !fips_strict
|
||||
|
||||
package ptls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
)
|
||||
|
||||
func Default(rootCAs *x509.CertPool) *tls.Config {
|
||||
return &tls.Config{
|
||||
// Can't use SSLv3 because of POODLE and BEAST
|
||||
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
|
||||
// Can't use TLSv1.1 because of RC4 cipher usage
|
||||
//
|
||||
// The Kubernetes API Server must use TLS 1.2, at a minimum,
|
||||
// to protect the confidentiality of sensitive data during electronic dissemination.
|
||||
// https://stigviewer.com/stig/kubernetes/2021-06-17/finding/V-242378
|
||||
MinVersion: tls.VersionTLS12,
|
||||
|
||||
// the order does not matter in go 1.17+ https://go.dev/blog/tls-cipher-suites
|
||||
// we match crypto/tls.cipherSuitesPreferenceOrder because it makes unit tests easier to write
|
||||
// this list is ignored when TLS 1.3 is used
|
||||
//
|
||||
// as of 2021-10-19, Mozilla Guideline v5.6, Go 1.17.2, intermediate configuration, supports:
|
||||
// - Firefox 27
|
||||
// - Android 4.4.2
|
||||
// - Chrome 31
|
||||
// - Edge
|
||||
// - IE 11 on Windows 7
|
||||
// - Java 8u31
|
||||
// - OpenSSL 1.0.1
|
||||
// - Opera 20
|
||||
// - Safari 9
|
||||
// https://ssl-config.mozilla.org/#server=go&version=1.17.2&config=intermediate&guideline=5.6
|
||||
//
|
||||
// The Kubernetes API server must use approved cipher suites.
|
||||
// https://stigviewer.com/stig/kubernetes/2021-06-17/finding/V-242418
|
||||
CipherSuites: []uint16{
|
||||
// these are all AEADs with ECDHE, some use ChaCha20Poly1305 while others use AES-GCM
|
||||
// this provides forward secrecy, confidentiality and authenticity of data
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
},
|
||||
|
||||
// enable HTTP2 for go's 1.7 HTTP Server
|
||||
// setting this explicitly is only required in very specific circumstances
|
||||
// it is simpler to just set it here than to try and determine if we need to
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
|
||||
// optional root CAs, nil means use the host's root CA set
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultLDAP(rootCAs *x509.CertPool) *tls.Config {
|
||||
c := Default(rootCAs)
|
||||
// add less secure ciphers to support the default AWS Active Directory config
|
||||
c.CipherSuites = append(c.CipherSuites,
|
||||
// CBC with ECDHE
|
||||
// this provides forward secrecy and confidentiality of data but not authenticity
|
||||
// MAC-then-Encrypt CBC ciphers are susceptible to padding oracle attacks
|
||||
// See https://crypto.stackexchange.com/a/205 and https://crypto.stackexchange.com/a/224
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
)
|
||||
return c
|
||||
}
|
||||
144
internal/crypto/ptls/profiles.go
Normal file
144
internal/crypto/ptls/profiles.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !fips_strict
|
||||
|
||||
package ptls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
// init prints a log message to tell the operator how Pinniped was compiled. This makes it obvious
|
||||
// that they are using Pinniped in FIPS-mode or not, which is otherwise hard to observe.
|
||||
func init() { //nolint:gochecknoinits
|
||||
switch filepath.Base(os.Args[0]) {
|
||||
case "pinniped-server", "pinniped-supervisor", "pinniped-concierge", "pinniped-concierge-kube-cert-agent":
|
||||
default:
|
||||
return // do not print FIPS logs if we cannot confirm that we are running a server binary
|
||||
}
|
||||
|
||||
// this init runs before we have parsed our config to determine our log level
|
||||
// thus we must use a log statement that will always print instead of conditionally print
|
||||
plog.Always("this server was not compiled in FIPS-only mode",
|
||||
"go version", runtime.Version())
|
||||
}
|
||||
|
||||
// SecureTLSConfigMinTLSVersion is the minimum tls version in the format expected by tls.Config.
|
||||
const SecureTLSConfigMinTLSVersion = tls.VersionTLS13
|
||||
|
||||
// Default TLS profile should be used by:
|
||||
// A. servers whose clients are outside our control and who may reasonably wish to use TLS 1.2, and
|
||||
// B. clients who need to interact with servers that might not support TLS 1.3.
|
||||
// Note that this will behave differently when compiled in FIPS mode (see profiles_fips_strict.go).
|
||||
func Default(rootCAs *x509.CertPool) *tls.Config {
|
||||
return &tls.Config{
|
||||
// Can't use SSLv3 because of POODLE and BEAST
|
||||
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
|
||||
// Can't use TLSv1.1 because of RC4 cipher usage
|
||||
//
|
||||
// The Kubernetes API Server must use TLS 1.2, at a minimum,
|
||||
// to protect the confidentiality of sensitive data during electronic dissemination.
|
||||
// https://stigviewer.com/stig/kubernetes/2021-06-17/finding/V-242378
|
||||
MinVersion: tls.VersionTLS12,
|
||||
|
||||
// the order does not matter in go 1.17+ https://go.dev/blog/tls-cipher-suites
|
||||
// we match crypto/tls.cipherSuitesPreferenceOrder because it makes unit tests easier to write
|
||||
// this list is ignored when TLS 1.3 is used
|
||||
//
|
||||
// as of 2021-10-19, Mozilla Guideline v5.6, Go 1.17.2, intermediate configuration, supports:
|
||||
// - Firefox 27
|
||||
// - Android 4.4.2
|
||||
// - Chrome 31
|
||||
// - Edge
|
||||
// - IE 11 on Windows 7
|
||||
// - Java 8u31
|
||||
// - OpenSSL 1.0.1
|
||||
// - Opera 20
|
||||
// - Safari 9
|
||||
// https://ssl-config.mozilla.org/#server=go&version=1.17.2&config=intermediate&guideline=5.6
|
||||
//
|
||||
// The Kubernetes API server must use approved cipher suites.
|
||||
// https://stigviewer.com/stig/kubernetes/2021-06-17/finding/V-242418
|
||||
CipherSuites: []uint16{
|
||||
// these are all AEADs with ECDHE, some use ChaCha20Poly1305 while others use AES-GCM
|
||||
// this provides forward secrecy, confidentiality and authenticity of data
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
},
|
||||
|
||||
// enable HTTP2 for go's 1.7 HTTP Server
|
||||
// setting this explicitly is only required in very specific circumstances
|
||||
// it is simpler to just set it here than to try and determine if we need to
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
|
||||
// optional root CAs, nil means use the host's root CA set
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultLDAP TLS profile should be used by clients who need to interact with potentially old LDAP servers
|
||||
// that might not support TLS 1.3 and that might use older ciphers.
|
||||
// Note that this will behave differently when compiled in FIPS mode (see profiles_fips_strict.go).
|
||||
func DefaultLDAP(rootCAs *x509.CertPool) *tls.Config {
|
||||
c := Default(rootCAs)
|
||||
// add less secure ciphers to support the default AWS Active Directory config
|
||||
c.CipherSuites = append(c.CipherSuites,
|
||||
// CBC with ECDHE
|
||||
// this provides forward secrecy and confidentiality of data but not authenticity
|
||||
// MAC-then-Encrypt CBC ciphers are susceptible to padding oracle attacks
|
||||
// See https://crypto.stackexchange.com/a/205 and https://crypto.stackexchange.com/a/224
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
)
|
||||
return c
|
||||
}
|
||||
|
||||
// Secure TLS profile should be used by:
|
||||
// A. servers whose clients are entirely known by us and who may reasonably be told that they must use TLS 1.3, and
|
||||
// B. clients who only need to interact with servers that are known by us to support TLS 1.3 (e.g. the Kubernetes API).
|
||||
// Note that this will behave differently when compiled in FIPS mode (see profiles_fips_strict.go).
|
||||
func Secure(rootCAs *x509.CertPool) *tls.Config {
|
||||
// as of 2021-10-19, Mozilla Guideline v5.6, Go 1.17.2, modern configuration, supports:
|
||||
// - Firefox 63
|
||||
// - Android 10.0
|
||||
// - Chrome 70
|
||||
// - Edge 75
|
||||
// - Java 11
|
||||
// - OpenSSL 1.1.1
|
||||
// - Opera 57
|
||||
// - Safari 12.1
|
||||
// https://ssl-config.mozilla.org/#server=go&version=1.17.2&config=modern&guideline=5.6
|
||||
c := Default(rootCAs)
|
||||
c.MinVersion = SecureTLSConfigMinTLSVersion // max out the security
|
||||
c.CipherSuites = nil // TLS 1.3 ciphers are not configurable
|
||||
return c
|
||||
}
|
||||
|
||||
// SecureServing modifies the given options to have the appropriate MinTLSVersion and CipherSuites.
|
||||
// This function should only be used by the implementation of ptls.SecureRecommendedOptions, which
|
||||
// is called to help configure our aggregated API servers. This exists only because it needs
|
||||
// to behave differently in FIPS mode.
|
||||
// This function is only public so we can integration test it in ptls_fips_test.go.
|
||||
// Note that this will behave differently when compiled in FIPS mode (see profiles_fips_strict.go).
|
||||
func SecureServing(opts *options.SecureServingOptionsWithLoopback) {
|
||||
// secureServingOptionsMinTLSVersion is the minimum tls version in the format
|
||||
// expected by SecureServingOptions.MinTLSVersion from
|
||||
// k8s.io/apiserver/pkg/server/options.
|
||||
opts.MinTLSVersion = "VersionTLS13"
|
||||
opts.CipherSuites = nil
|
||||
}
|
||||
@@ -20,9 +20,7 @@ import (
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
// Until goboring supports TLS 1.3, use TLS 1.2.
|
||||
const SecureTLSConfigMinTLSVersion = tls.VersionTLS12
|
||||
|
||||
// init: see comment in profiles.go.
|
||||
func init() {
|
||||
switch filepath.Base(os.Args[0]) {
|
||||
case "pinniped-server", "pinniped-supervisor", "pinniped-concierge", "pinniped-concierge-kube-cert-agent":
|
||||
@@ -32,9 +30,16 @@ func init() {
|
||||
|
||||
// this init runs before we have parsed our config to determine our log level
|
||||
// thus we must use a log statement that will always print instead of conditionally print
|
||||
plog.Always("using boring crypto in fips only mode", "go version", runtime.Version())
|
||||
plog.Always("this server was compiled to use boring crypto in FIPS-only mode",
|
||||
"go version", runtime.Version())
|
||||
}
|
||||
|
||||
// SecureTLSConfigMinTLSVersion: see comment in profiles.go.
|
||||
// Until goboring supports TLS 1.3, use TLS 1.2.
|
||||
const SecureTLSConfigMinTLSVersion = tls.VersionTLS12
|
||||
|
||||
// Default: see comment in profiles.go.
|
||||
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
|
||||
func Default(rootCAs *x509.CertPool) *tls.Config {
|
||||
return &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
@@ -64,16 +69,22 @@ func Default(rootCAs *x509.CertPool) *tls.Config {
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultLDAP: see comment in profiles.go.
|
||||
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
|
||||
func DefaultLDAP(rootCAs *x509.CertPool) *tls.Config {
|
||||
return Default(rootCAs)
|
||||
}
|
||||
|
||||
// Secure: see comment in profiles.go.
|
||||
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
|
||||
// Until goboring supports TLS 1.3, make the Secure profile the same as the Default profile in FIPS mode.
|
||||
func Secure(rootCAs *x509.CertPool) *tls.Config {
|
||||
return Default(rootCAs)
|
||||
}
|
||||
|
||||
func DefaultLDAP(rootCAs *x509.CertPool) *tls.Config {
|
||||
return Default(rootCAs)
|
||||
}
|
||||
|
||||
// Until goboring supports TLS 1.3, make secureServing use the same as the defaultServing profile in FIPS mode.
|
||||
func secureServing(opts *options.SecureServingOptionsWithLoopback) {
|
||||
// SecureServing: see comment in profiles.go.
|
||||
// This chooses different cipher suites and/or TLS versions compared to non-FIPS mode.
|
||||
// Until goboring supports TLS 1.3, make SecureServing use the same as the defaultServing profile in FIPS mode.
|
||||
func SecureServing(opts *options.SecureServingOptionsWithLoopback) {
|
||||
defaultServing(opts)
|
||||
}
|
||||
9
internal/crypto/ptls/profiles_fips_strict_test.go
Normal file
9
internal/crypto/ptls/profiles_fips_strict_test.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package ptls
|
||||
|
||||
// If you are coming here to look for unit tests for the FIPS-mode profiles,
|
||||
// please instead see test/integration/ptls_fips_test.go.
|
||||
// CI does not currently run the unit tests in FIPS mode, so these unit tests
|
||||
// were instead written as integration tests.
|
||||
91
internal/crypto/ptls/profiles_test.go
Normal file
91
internal/crypto/ptls/profiles_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package ptls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
)
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aCertPool := x509.NewCertPool()
|
||||
|
||||
actual := Default(aCertPool)
|
||||
expected := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
},
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
RootCAs: aCertPool,
|
||||
}
|
||||
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestDefaultLDAP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aCertPool := x509.NewCertPool()
|
||||
|
||||
actual := DefaultLDAP(aCertPool)
|
||||
expected := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, //nolint:gosec // this is a test
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
RootCAs: aCertPool,
|
||||
}
|
||||
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestSecure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aCertPool := x509.NewCertPool()
|
||||
|
||||
actual := Secure(aCertPool)
|
||||
expected := &tls.Config{
|
||||
MinVersion: tls.VersionTLS13,
|
||||
CipherSuites: nil, // TLS 1.3 ciphers are not configurable
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
RootCAs: aCertPool,
|
||||
}
|
||||
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestSecureServing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := &options.SecureServingOptionsWithLoopback{SecureServingOptions: &options.SecureServingOptions{}}
|
||||
SecureServing(opts)
|
||||
require.Equal(t, options.SecureServingOptionsWithLoopback{
|
||||
SecureServingOptions: &options.SecureServingOptions{
|
||||
MinTLSVersion: "VersionTLS13",
|
||||
},
|
||||
}, *opts)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func DefaultRecommendedOptions(opts *options.RecommendedOptions, f RestConfigFun
|
||||
// certain well known clients which we expect will always use modern TLS settings (like the Kube API server).
|
||||
// It returns a PrepareServerConfigFunc which must be used on a RecommendedConfig before passing it to RecommendedOptions.ApplyTo().
|
||||
func SecureRecommendedOptions(opts *options.RecommendedOptions, f RestConfigFunc) (PrepareServerConfigFunc, error) {
|
||||
secureServing(opts.SecureServing)
|
||||
SecureServing(opts.SecureServing)
|
||||
return secureClient(opts, f)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,18 +34,6 @@ func TestDefaultServing(t *testing.T) {
|
||||
}, *opts)
|
||||
}
|
||||
|
||||
func TestSecureServing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opts := &options.SecureServingOptionsWithLoopback{SecureServingOptions: &options.SecureServingOptions{}}
|
||||
secureServing(opts)
|
||||
require.Equal(t, options.SecureServingOptionsWithLoopback{
|
||||
SecureServingOptions: &options.SecureServingOptions{
|
||||
MinTLSVersion: "VersionTLS13",
|
||||
},
|
||||
}, *opts)
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !fips_strict
|
||||
|
||||
package ptls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
)
|
||||
|
||||
// secureServingOptionsMinTLSVersion is the minimum tls version in the format
|
||||
// expected by SecureServingOptions.MinTLSVersion from
|
||||
// k8s.io/apiserver/pkg/server/options.
|
||||
const secureServingOptionsMinTLSVersion = "VersionTLS13"
|
||||
|
||||
// SecureTLSConfigMinTLSVersion is the minimum tls version in the format expected
|
||||
// by tls.Config.
|
||||
const SecureTLSConfigMinTLSVersion = tls.VersionTLS13
|
||||
|
||||
func Secure(rootCAs *x509.CertPool) *tls.Config {
|
||||
// as of 2021-10-19, Mozilla Guideline v5.6, Go 1.17.2, modern configuration, supports:
|
||||
// - Firefox 63
|
||||
// - Android 10.0
|
||||
// - Chrome 70
|
||||
// - Edge 75
|
||||
// - Java 11
|
||||
// - OpenSSL 1.1.1
|
||||
// - Opera 57
|
||||
// - Safari 12.1
|
||||
// https://ssl-config.mozilla.org/#server=go&version=1.17.2&config=modern&guideline=5.6
|
||||
c := Default(rootCAs)
|
||||
c.MinVersion = SecureTLSConfigMinTLSVersion // max out the security
|
||||
c.CipherSuites = nil // TLS 1.3 ciphers are not configurable
|
||||
return c
|
||||
}
|
||||
|
||||
func secureServing(opts *options.SecureServingOptionsWithLoopback) {
|
||||
opts.MinTLSVersion = secureServingOptionsMinTLSVersion
|
||||
opts.CipherSuites = nil
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Multiple Identity Providers"
|
||||
authors: [ "@cfryanr" ]
|
||||
status: "accepted"
|
||||
status: "implemented"
|
||||
sponsor: []
|
||||
approval_date: "July 12, 2023"
|
||||
---
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: "Concierge Impersonation Proxy | External Certificate Management"
|
||||
authors: [ "@joshuatcasey" ]
|
||||
status: "in-review"
|
||||
status: "implemented"
|
||||
sponsor: [ "@cfryanr", "@benjaminapetersen" ]
|
||||
approval_date: ""
|
||||
approval_date: "August 8, 2023"
|
||||
---
|
||||
|
||||
*Disclaimer*: Proposals are point-in-time designs and decisions.
|
||||
|
||||
581
proposals/1859_github-auth/README.md
Normal file
581
proposals/1859_github-auth/README.md
Normal file
@@ -0,0 +1,581 @@
|
||||
---
|
||||
title: "Authenticating Users via GitHub"
|
||||
authors: [ "@cfryanr" ]
|
||||
status: "accepted"
|
||||
sponsor: [ ]
|
||||
approval_date: "March 27, 2024"
|
||||
---
|
||||
|
||||
*Disclaimer*: Proposals are point-in-time designs and decisions.
|
||||
Once approved and implemented, they become historical documents.
|
||||
If you are reading an old proposal, please be aware that the
|
||||
features described herein might have continued to evolve since.
|
||||
|
||||
# Authenticating Users via GitHub
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Many developers in the world have a GitHub account, and many enterprises use GitHub.
|
||||
This makes GitHub a convenient identity provider for an enterprise to use to control
|
||||
access to Kubernetes clusters for its developers.
|
||||
|
||||
This document proposes adding a GitHubIdentityProvider resource to the Pinniped Supervisor,
|
||||
which would allow GitHub to be used as an identity provider for authentication to Kubernetes
|
||||
clusters.
|
||||
|
||||
### How Pinniped Works Today (as of version v0.28.0)
|
||||
|
||||
The Pinniped Supervisor currently supports OIDC-compliant identity providers and
|
||||
LDAP identity providers (including Active Directory). Unfortunately, GitHub does not offer
|
||||
either of these protocols for authenticating users. GitHub uses a slightly customized version
|
||||
of OAuth 2.0 for authenticating users, so Pinniped will need to add new features to support
|
||||
using GitHub as an identity provider.
|
||||
|
||||
## Terminology / Concepts
|
||||
|
||||
### GitHub OAuth 2.0 Clients
|
||||
|
||||
For web browser-based authentication, GitHub supports two different types of OAuth 2.0 clients (compared in
|
||||
[this doc](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/deciding-when-to-build-a-github-app)
|
||||
and [this doc](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/differences-between-github-apps-and-oauth-apps)):
|
||||
|
||||
- GitHub Apps (newer)
|
||||
- OAuth Apps (we will call these "GitHub OAuth Apps" below to differentiate them from the generic concept of an OAuth
|
||||
2.0 client)
|
||||
|
||||
Both flavors of Apps allow delegated authentication and authorization, but GitHub Apps are newer and allow
|
||||
GitHub Organization administrators to have more control over the management of the apps.
|
||||
|
||||
Astute readers may note that OAuth 2.0 is typically used only for delegated authorization, not authentication.
|
||||
GitHub's REST API enables the use of OAuth 2.0 for authentication by offering endpoints that can be used to
|
||||
learn about the user's identity from their OAuth 2.0 access token (see below).
|
||||
|
||||
### GitHub PATs
|
||||
|
||||
For CLI-based (no browser required) authentication, GitHub supports two different types of
|
||||
Personal Access Tokens (PATs) (compared in
|
||||
[this doc](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)):
|
||||
|
||||
- Fine-grained PATs (newer)
|
||||
- Classic PATs
|
||||
|
||||
Both flavors of PATs allow authenticating to the GitHub API, but Fine-grained PATs are newer and allow
|
||||
GitHub organization administrators to have more control over how the PAT can be used to access API resources
|
||||
related to the organization.
|
||||
|
||||
Note that at the time of writing this document, Fine-grained PATs are a Beta feature of GitHub, and are not
|
||||
enabled by default for use on an organization. The organization admin must opt in to allowing them, and may
|
||||
also choose to opt-out of allowing classic PATs.
|
||||
|
||||
### GitHub's REST API
|
||||
|
||||
GitHub offers a REST API which can be used to find out about, among other things, the
|
||||
[identity of the currently authenticated user](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user)
|
||||
(`/user`), the
|
||||
[organization memberships of the currently authenticated user](https://docs.github.com/en/rest/orgs/orgs?apiVersion=2022-11-28#list-organizations-for-the-authenticated-user)
|
||||
(`/user/orgs`), the
|
||||
[team memberships of the currently authenticated user](https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#list-teams-for-the-authenticated-user)
|
||||
(`/user/teams`).
|
||||
These are the only three GitHub APIs that would be used by Pinniped.
|
||||
|
||||
Access tokens from both types of OAuth 2.0 clients and both types PATs can be used to call the GitHub APIs
|
||||
on behalf of a GitHub user.
|
||||
|
||||
The permissions model for API calls authenticated via access tokens from GitHub OAuth Apps and Classic PATs are the
|
||||
same, and are based on granted scopes. Scopes are configured on the Classic PAT by the user who creates it.
|
||||
Scopes are not configured on a GitHub OAuth App, but are instead requested by the client at authorization time,
|
||||
and can be approved by the individual user at authorization time. To use the above APIs the client or PAT must be
|
||||
granted the `read:user` and `read:org` scopes.
|
||||
|
||||
The permissions model for API calls authenticated via access tokens from GitHub Apps and Fine-grained PATs are the same,
|
||||
and are based on GitHub's fine-grained permissions model. These do not use OAuth scopes. Fine-grained permissions
|
||||
are configured on the GitHub App or the Fine-grained PAT. To use the above APIs, the client or PAT must be configured
|
||||
to allow reading the organization's memberships. Additionally, the org owners may need to approve the GitHub App
|
||||
or Fine-grained PAT's permissions for that org.
|
||||
|
||||
All REST API calls are subject to per-user
|
||||
[rate limits](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28).
|
||||
|
||||
## Proposal
|
||||
|
||||
### Goals and Non-goals
|
||||
|
||||
Goals:
|
||||
|
||||
- Add new GitHubIdentityProvider resource which allows Supervisor admins to configure GitHub
|
||||
as an identity provider. Offer configuration options for extracting usernames and group memberships, as well
|
||||
as for preventing authentication unless a user belongs to a certain GitHub organization.
|
||||
- Allow GitHubIdentityProvider(s) to be configured by admins on FederationDomains, and enable all features of
|
||||
FederationDomains on them (e.g. identity transformations).
|
||||
- Allow end users to use a web browser-based authentication flow, similar to other supported identity providers,
|
||||
where the end user only needs to authenticate using a web browser once a day.
|
||||
- Allow end users to use a CLI-based authentication flow which does not require a web browser,
|
||||
similar to other supported identity providers. This enables using Pinniped authentication for scripting
|
||||
and CI purposes. Allow the admin to disable this feature if they prefer.
|
||||
- Regardless of which flow the user uses to authenticate, regularly check with GitHub to see if the user should
|
||||
be allowed to continue their session. If the user's GitHub tokens were revoked, or if the user no longer belongs
|
||||
to the configured GitHub organization(s), then end their Pinniped Supervisor session to block their continued
|
||||
access to Kubernetes clusters.
|
||||
- Regardless of which flow the user uses to authenticate, regularly check with GitHub to see if the user's group
|
||||
memberships have changed and update them accordingly.
|
||||
- Any client of the Supervisor should be able to authenticate GitHub users using the web browser-based
|
||||
authentication flow, including the Pinniped CLI and third-party clients registered with the Supervisor as OIDCClients.
|
||||
- The IDP chooser page and the IDP discovery endpoints should both include the configured GitHub identity provider(s)
|
||||
for the FederationDomain in the lists that they return.
|
||||
- Avoid requiring any changes to the Pinniped CLI or other clients. From a client's point of view,
|
||||
which external identity provider is used for authentication should be transparent.
|
||||
|
||||
### Specification / How it Solves the Use Cases
|
||||
|
||||
#### New GitHubIdentityProvider resource
|
||||
|
||||
Add a new CRD called GitHubIdentityProvider. Here is an example:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: idp.supervisor.pinniped.dev/v1alpha1
|
||||
kind: GitHubIdentityProvider
|
||||
metadata:
|
||||
namespace: supervisor
|
||||
name: github
|
||||
spec:
|
||||
githubAPI:
|
||||
# Required only for GitHub Enterprise Server (on-prem).
|
||||
# Defaults to using the GitHub's public API.
|
||||
# Can be hostname, IPv4 address, or IPv6 address.
|
||||
# May optionally end with `:port` for some port number.
|
||||
# Note that an IPv6 address must not be surrounded by
|
||||
# square brackets unless it has a port number.
|
||||
host: my.example.com
|
||||
# X.509 Certificate Authority (base64-encoded PEM bundle).
|
||||
# If omitted, a default set of system roots will be trusted.
|
||||
# Required only for GitHub Enterprise Server (on-prem), and only
|
||||
# when its CA will not be trusted by the default system roots.
|
||||
certificateAuthorityData: LS0tLS1CRUdJTiBDRV...0tLS0tCg==
|
||||
claims:
|
||||
# Which property of the GitHub user record shall determine
|
||||
# the username in Kubernetes. Can be either "id", "login",
|
||||
# or "login:id".
|
||||
# Login names can only contain alphanumeric characters and
|
||||
# non-repeating hyphens, and may not start or end with hyphens.
|
||||
# GitHub's users are allowed to change their login name,
|
||||
# although it is inconvenient. If a GitHub user changed
|
||||
# their login name from "foo" to "bar", then a second user
|
||||
# might change their name from "baz" to "foo" in order
|
||||
# to take the old username of the first user. For this
|
||||
# reason, it is not as safe to make authorization decisions
|
||||
# based only on the login name.
|
||||
# If desired, an admin could configure identity
|
||||
# transformation expressions on a FederationDomain to
|
||||
# further customize these usernames.
|
||||
# Defaults to "login:id", which is the login name, followed
|
||||
# by a colon, followed by the unique and unchanging integer
|
||||
# ID number. This blends human-readable login names with the
|
||||
# unchanging ID value. Note that colons are not allowed in
|
||||
# GitHub login names or in ID numbers, so the colon in the
|
||||
# "login:id" name will always be the login/id separator colon.
|
||||
username: id
|
||||
# Which property of the GitHub team record shall determine
|
||||
# the group name in Kubernetes. Can be either "name" or "slug".
|
||||
# Team names can contain upper and lower case characters,
|
||||
# whitespace, and punctuation (e.g. "Kube admins!").
|
||||
# Slug names are lower case alphanumeric characters and may
|
||||
# contain dashes and underscores (e.g. "kube-admins").
|
||||
# Either way, group names will always be prefixed by the
|
||||
# GitHub org name followed by a slash (e.g. my-org/my-team).
|
||||
# Note that slashes are not allowed in GitHub org names,
|
||||
# so the first slash in the group name will always be the
|
||||
# org/team separator slash.
|
||||
# Groups names will only include the teams which are
|
||||
# returned by the GitHub API's /user/teams endpoint,
|
||||
# which are the teams to which the user directly belongs
|
||||
# and the immediate parent teams of those teams
|
||||
# (and Pinniped will remove duplicates from the list).
|
||||
# The entire hierarchy of nested team memberships is not
|
||||
# returned by the GitHub API, and so will not be
|
||||
# reflected in the user's group memberships.
|
||||
# Also, the GitHub OAuth App or GitHub App must have
|
||||
# permission to see the teams in a particular org,
|
||||
# or else none of the teams from that org will be
|
||||
# reflected in the user's group memberships.
|
||||
# If desired, an admin could configure identity
|
||||
# transformation expressions on a FederationDomain to
|
||||
# further customize these group names.
|
||||
# Defaults to "slug".
|
||||
groups: slug
|
||||
allowAuthentication:
|
||||
organizations:
|
||||
# Optionally reject any user who attempts to authenticate
|
||||
# unless they belong to one of these GitHub orgs.
|
||||
# The GitHub App or GitHub OAuth App provided in the
|
||||
# spec.client.secretName must be allowed by the org
|
||||
# owners to view org memberships, or else the
|
||||
# user will not be considered to belong to that org
|
||||
# or belong to any teams within that org.
|
||||
# When specified, users' group memberships will be
|
||||
# filtered to include only those GitHub teams which
|
||||
# are owned by these orgs. org membership comparisons
|
||||
# shall be done as case-insensitive string comparisons,
|
||||
# but case shall be preserved in the org names when
|
||||
# they appear in downstream group names
|
||||
# If desired, an admin could configure identity
|
||||
# policy expressions on a FederationDomain to
|
||||
# further customize which users and groups are allowed
|
||||
# to authenticate.
|
||||
allowed: [ vmware-tanzu, broadcom ]
|
||||
# Policy decides how the allowed list will be used.
|
||||
# Defaults to OnlyUsersFromAllowedOrganizations,
|
||||
# which means that the user must belong to one
|
||||
# of the allowed list, and the allow list must
|
||||
# be non-empty. May be set to AllGitHubUsers,
|
||||
# which means any GitHub user can authenticate
|
||||
# regardless of org membership, and all the team
|
||||
# memberships returned by the GitHub API for that
|
||||
# user will be reflected as groups regardless of
|
||||
# which orgs own those teams. When set to AllGitHubUsers,
|
||||
# the allow list must be empty.
|
||||
policy: OnlyUsersFromAllowedOrganizations
|
||||
# Allow or disallow users to use GitHub Personal Access Tokens
|
||||
# for CLI-based (no web browser) authentication.
|
||||
# Setting these both to false disables all non-interactive
|
||||
# CLI-based auth, but still allows browser-based auth.
|
||||
personalAccessTokens:
|
||||
# Allow or disallow the use of GitHub Fine-grained PATs
|
||||
# to authenticate to Kubernetes clusters. If they
|
||||
# are disallowed for your GitHub org, then also
|
||||
# disallow them here so your users can get a nice
|
||||
# error message when trying to use a fine-grained PAT.
|
||||
# Defaults to false.
|
||||
fineGrained: true
|
||||
# Allow or disallow the use of GitHub Classic PATs
|
||||
# to authenticate to Kubernetes clusters. If they
|
||||
# are disallowed for your GitHub org, then also
|
||||
# disallow them here so your users can get a nice
|
||||
# error message when trying to use a classic PAT.
|
||||
# Defaults to false.
|
||||
classic: false
|
||||
client:
|
||||
# The name of Secret in the same namespace which holds the
|
||||
# client ID and client secret of a GitHub App or
|
||||
# Github OAuth App. This will only be used for web
|
||||
# browser-based auth flows (not CLI-based flows).
|
||||
# Required.
|
||||
secretName: github-client-credentials
|
||||
status:
|
||||
conditions: # Discussed below.
|
||||
phase: # Pending, Ready, or Error. To summarize the conditions.
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: secrets.pinniped.dev/github-client # must use this type
|
||||
metadata:
|
||||
namespace: supervisor
|
||||
name: github-client-credentials
|
||||
data:
|
||||
clientID: <some-github-client-id>
|
||||
clientSecret: <some-github-client-secret>
|
||||
```
|
||||
|
||||
The status conditions will be populated by a controller. The conditions will include:
|
||||
- `HostValid`. This is not a URL, so it must not contain `://`. It must be parsable as defined by our `endpointaddr.Parse()` helper function.
|
||||
- `TLSConfigurationValid`. The CA bundle can be parsed as a CA bundle.
|
||||
- `OrganizationsPolicyValid`. Use the same logic and same error text in CEL validations for this field, and repeat it in the controller in case an old version of Kubernetes is being used.
|
||||
- `ClientCredentialsValid`. This is what we call the same check in the OIDCIdentityProvider's status.
|
||||
- `GitHubConnectionValid`. Report if the host can be dialed with TLS verification. (In LDAPIdentityProvider we called this `LDAPConnectionValid`.)
|
||||
|
||||
#### Web Browser-based Authentication using GitHubIdentityProvider
|
||||
|
||||
The client ID and client secret configured on the GitHubIdentityProvider can be for either a GitHub App or a
|
||||
GitHub OAuth App. This flexibility can be made possible because there are many similarities in the
|
||||
OAuth 2.0 authorization flow used by both. To the extent that we can ignore the differences, Pinniped
|
||||
will not need to know if it is acting as a GitHub App or a GitHub OAuth App.
|
||||
|
||||
The authorization flow for GitHub apps is described in
|
||||
[this doc](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-user-access-token-for-a-github-app)
|
||||
and the flow for GitHub OAuth Apps is described in
|
||||
[this doc](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps).
|
||||
|
||||
From a client's point of view, the main difference is that access tokens for a GitHub App may expire, depending how the
|
||||
app was configured on GitHub. However, if the access token expires, then it is always after 8 hours. That duration is
|
||||
not currently configurable. This gives Pinniped a nice option to save implementation time and complexity: we can simply
|
||||
ignore GitHub's OAuth refresh flow. The user's Pinniped session should expire either when their GitHub access token
|
||||
stops working (i.e. was expired or revoked) or whenever Pinniped's hardcoded 9-hour session limit is reached.
|
||||
If it is an expiring access token, then the session will end after 8 hours due to the token expiration.
|
||||
If it is not an expiring access token, then the session will end after 9 hours. That's close enough to Pinniped's
|
||||
concept of "once-per 9 hours authentication" that it's not worth the extra effort to implement the refresh
|
||||
flow with GitHub.
|
||||
|
||||
The other difference from a client's point of view is that GitHub Apps do not use scopes. However, the `scope` param
|
||||
may be sent on the authorization request for either a GitHub App or a GitHub OAuth App the same way, and the
|
||||
authorization endpoint will simply ignore it for a GitHub App. The token response for a GitHub app will not
|
||||
include the list of granted scopes, so we would need to ignore that too. If we request scopes but ignore
|
||||
validating that the requested scopes were actually granted, then we can treat GitHub Apps and GitHub OAuth Apps
|
||||
interchangeably.
|
||||
|
||||
There are other differences which would need to be documented for the user who is configuring the GitHub App or
|
||||
GitHub OAuth App, but those differences would not change the Pinniped code. The user would need to know how
|
||||
each of these two app types are installed or approved by a GitHub org, because without taking the appropriate
|
||||
steps for the org, the `/user/orgs` GitHub API will not return any results related to that org.
|
||||
This would prevent users of that org from being able to authenticate, because Pinniped would not be
|
||||
able to discover that they are members of that org.
|
||||
|
||||
During initial login (authorization) the Supervisor can get an access token from GitHub and use it to learn the
|
||||
identity, org memberships, and team memberships of that user by calling the GitHub API. It can then store that
|
||||
access token into the Supervisor's session storage. During the Supervisor session refresh flow, it can make the same
|
||||
calls to the GitHub API to ensure that the access token is still valid, the user's identity has not changed,
|
||||
the user's org membership has not changed, the user's org membership still meets the critera defined in the
|
||||
`allowedOrganziations` list, and to update the users groups by getting their current team memberships.
|
||||
|
||||
The three GitHub API requests during login, and the three more GitHub API requests during each refresh, will all count
|
||||
against that GitHub user's hourly API request limits. Note that refreshes only happen when users are actively
|
||||
interacting with Kubernetes clusters during their session.
|
||||
For one session started using browser-based authentication, and used actively throughout an hour, this would
|
||||
be a maximum of approximately 36 GitHub API requests per hour against the user's 5,000 requests per hour limit.
|
||||
Additionally, each new concurrent Supervisor session started
|
||||
by that same user will cause the same API calls again (new sessions are started when the user's home directory is not
|
||||
shared between them, e.g. because the user is authenticating from multiple computers at the same time).
|
||||
|
||||
#### CLI-based Authentication using GitHubIdentityProvider
|
||||
|
||||
Non-interactive authentication at the CLI (no web browser) is desirable for scripting and CI/CD use cases.
|
||||
Some enterprises prefer to create "bot" users in their regular identity providers and use Pinniped for authentication,
|
||||
rather than using Kubernetes' other features for authentication of non-human actors. OIDCIdentityProviders,
|
||||
LDAPIdentityProviders, and ActiveDirectoryIdentityProviders already support CLI-based authentication, so it would be
|
||||
preferable for GitHubIdentityProvider to also support it, if possible. In a CLI-based flow, the CLI collects the
|
||||
user's username and password from the user, and no web browser is needed.
|
||||
|
||||
Note that CLI-based (non-interactive) authentication is only allowed on the `pinniped-cli` client, not on any
|
||||
configured third-party OIDCClient. This pre-existing limitation is to prevent 3rd party apps from directly handling
|
||||
the user's credential.
|
||||
|
||||
This feature will not be included in the initial release of GitHub support. It is planned as a follow-up feature
|
||||
after web-browser based authentication is fully supported. Therefore, some of the details in this section may
|
||||
change before implementation. This section is meant to give an overview of the general intended approach.
|
||||
|
||||
Since we will use the GitHub API to discover a user's identity, we could use a PAT provided by the user
|
||||
at the CLI to authenticate that user. To keep the experience (and code) similar to OIDC and LDAP/AD identity providers,
|
||||
the CLI could interactively prompt for their username and password, and the user could paste the PAT as their password.
|
||||
To avoid the prompts, the user could set the existing `PINNIPED_USERNAME` and `PINNIPED_PASSWORD` environment variables
|
||||
that are already used to skips the prompts for OIDC and LDAP/AD password-based authentication. Note that the PAT
|
||||
indirectly identifies the username by itself, but we could still require that the user submit their GitHub username
|
||||
during CLI-based authentication to keep the client-side code and user experience consistent with OIDCIdentityProviders,
|
||||
LDAPIdentityProviders, and ActiveDirectoryIdentityProviders, which all require both username and password
|
||||
for CLI-based authentication.
|
||||
|
||||
Pinniped could accept either Fine-grained PATs or Classic PATs (or both), at the discretion of the Supervisor admin.
|
||||
PATs of each of these types have different prefixes and can be distinguished from each other by the prefix.
|
||||
Fine-grained PATs would need to be created with the permission to read org memberships. Classic PATs would need to
|
||||
be created to allow the `read:user` and `read:org` scopes.
|
||||
|
||||
However, using PATs for this purpose would have some important implications:
|
||||
|
||||
1. At first glance, it might appear that the Supervisor would need to hold the PAT in its session storage
|
||||
(until the Supervisor session has expired) so it can validate the user again upon session refresh.
|
||||
However, this PAT would be usable to grant access to Kubernetes
|
||||
clusters to anyone who holds it, similar to an end-user password for CLI-based (non-interactive) OIDC or
|
||||
LDAP/AD logins. The Supervisor does not store those OIDC/LDAP/AD passwords, and it should not store these
|
||||
PATs either, if possible.
|
||||
2. PATs have no concept of intended audience, so there is no way for a user to consent that one particular PAT is
|
||||
intended to control their access to Kubernetes clusters. A GitHub user can have many PATs for different purposes,
|
||||
and we should not treat them as interchangeable.
|
||||
|
||||
Solving the above points are key to designing CLI-based authentication for GitHubIdentityProviders.
|
||||
|
||||
##### How to Avoid Storing PATs in the Supervisor
|
||||
|
||||
We could avoid storing PATs on the Supervisor by simply preventing token refreshes for any Supervisor sessions that
|
||||
were started using a GitHub PAT. There's no need to store the PAT for the purposes of refreshing the session if
|
||||
there is no concept of refreshing these sessions. By always returning an error on these refresh requests,
|
||||
the Pinniped CLI will automatically submit the PAT again (if stored in the user's env vars for the CLI process)
|
||||
to start a new session. There will be no observable user experience impact from always rejecting refreshes.
|
||||
|
||||
By disabling refresh for these sessions, these users, when actively interacting with Kubernetes clusters, will
|
||||
create many more sessions per hour compared to users who can refresh their current sessions. Typical usage of
|
||||
Pinniped grants access to Kubernetes clusters for about five minutes before the next session refresh.
|
||||
This will cause two concerns:
|
||||
|
||||
1. Each new session creates several Kubernetes Secrets for Supervisor session storage, so increasing the number of
|
||||
sessions will increase the number of Secrets.
|
||||
2. Each new session will need to make several calls to the GitHub API, impacting the GitHub user's hourly rate limit.
|
||||
|
||||
We can mitigate both effects using the techniques outlined in https://github.com/vmware-tanzu/pinniped/pull/1857.
|
||||
By making the access token lifetime slightly longer, the user will be able to use their clusters for about 10 minutes,
|
||||
rather than 5 minutes, reducing the number of new sessions that they need to start per hour.
|
||||
By garbage collecting the session storage much faster for these sessions, the number of session storage Secrets
|
||||
on the Supervisor's cluster will remain manageable.
|
||||
|
||||
For one user using CLI-based authentication, and actively making requests to Kubernetes clusters
|
||||
throughout an hour, this would be a maximum of approximately 18 GitHub API requests per hour against the
|
||||
user's 5,000 requests per hour limit.
|
||||
|
||||
##### How to Avoid Treating All PATs Interchangeably
|
||||
|
||||
Pinniped could offer a new feature to allow an end user to consent to use a particular PAT to enable
|
||||
Kubernetes authentication for that user for that GitHubIdentityProvider for that FederationDomain.
|
||||
|
||||
1. This would be technically possible if the user authenticated to the Supervisor first using a
|
||||
browser-based GitHub authentication flow, and then user called an API on the Supervisor FederationDomain
|
||||
(using their FederationDomain-issued access token to prove their identity)
|
||||
to consent to having a specific PAT be used for Kubernetes authentication in the future.
|
||||
|
||||
2. The user should be advised that anyone who holds the PAT will be able to authenticate using their
|
||||
identity to all Kubernetes clusters in the FederationDomain. Advise the user that the PAT:
|
||||
- should be treated like a password to Kubernetes clusters and therefore should never be shared with other people,
|
||||
- should be kept in a safe place such as a password manager,
|
||||
- should never be used for any other purpose aside from authenticating with this FederationDomain,
|
||||
- would ideally have an expiration date set on it (not required),
|
||||
- should never have any extra permissions/scopes besides the minimum required by the Pinniped docs,
|
||||
- and should be immediately revoked if accidentally leaked or shared.
|
||||
|
||||
These are the responsibility of the user and cannot be enforced by Pinniped.
|
||||
|
||||
3. The implementation of the consent endpoint could validate that the currently authenticated Supervisor user is from
|
||||
GitHub. Then it could call the GitHub API using the submitted PAT to validate that
|
||||
that the current Supervisor user matches the user identified by the PAT. Then it could store the SHA-256 hash
|
||||
of the PAT into an allow list of consented PATs for that user for that FederationDomain for
|
||||
future CLI-based authentication attempts.
|
||||
|
||||
4. Other similar endpoints could be used to revoke consents for PATs.
|
||||
Ideally a user could have multiple consented PATs registered at the same time
|
||||
to allow them to rotate PATs gracefully. The user should also be able to revoke consent for all PATs,
|
||||
in case they have lost or forgotten a PAT.
|
||||
|
||||
5. This consent would need to be durably stored by the Supervisor outside of session storage, since it would be used
|
||||
to help start future sessions based on the PAT. This could be done using Kubernetes Secrets. One Secret
|
||||
per-FederationDomain could hold a map of all usernames with the hashes of all consented PATs for each username.
|
||||
|
||||
The new Supervisor API endpoints would be:
|
||||
|
||||
- `POST https://<federation_domain_issuer_string>/v1alpha1/consent/githubpats` to add a new PAT
|
||||
- `DELETE https://<federation_domain_issuer_string>/v1alpha1/consent/githubpats/<sha_256_hash_of_pat>`
|
||||
to remove a specific PAT
|
||||
- `DELETE https://<federation_domain_issuer_string>/v1alpha1/consent/githubpats` to remove all PATs
|
||||
|
||||
New Pinniped CLI commands could be added to wrap these consent APIs, and to help the user authenticate with
|
||||
the Supervisor before calling these APIs. Note that the user would typically need a kubeconfig to authenticate with
|
||||
a Supervisor today, because that kubeconfig has the URL, CA, etc. required to start the authentication attempt.
|
||||
For convenience, these new CLI commands would need a Pinniped-compatible kubeconfig for a cluster
|
||||
which is using the same FederationDomain from which to read those settings. It can ignore the portions of
|
||||
the kubeconfig which identify the Kubernetes cluster itself, and only pay attention to the portions which
|
||||
relate to the Supervisor. These commands would accept the typical methods to choose a current kubeconfig
|
||||
(e.g. `--kubeconfig` flag, `KUBECONFIG` env var, etc.).
|
||||
|
||||
These CLI commands would be:
|
||||
|
||||
- `pinniped consent github-pat add <pat>` to add a PAT to the consent list for the current user
|
||||
for this FederationDomain
|
||||
- `pinniped consent github-pat remove <pat>` to remove a specific PAT from the consent list for the current user
|
||||
for this FederationDomain
|
||||
- `pinniped consent github-pat remove-all` to remove all PATs from the consent list for the current user
|
||||
for this FederationDomain
|
||||
|
||||
The implementation of these CLI commands would be as follows:
|
||||
|
||||
1. Read the credential exec specification from the kubeconfig. Confirm that it has the right flags to be
|
||||
an invocation of the "pinniped login oidc" command. Remember the value of the flags that are related to
|
||||
the Pinniped Supervisor (e.g. `--issuer`, `--ca-bundle-data`, `--client-id`, `--scopes`, `--request-audience`, and
|
||||
`--upstream-identity-provider-*`).
|
||||
2. This command could only work if the issuer specified by these flags is a Pinniped Supervisor.
|
||||
The command could call the issuer's discovery endpoint to confirm that it is a Supervisor and to get the
|
||||
location of its PAT consent endpoint (assuming that it is a new enough Supervisor to have this endpoint).
|
||||
3. The command would need to trigger a Pinniped login or refresh flow because it needs to be sure that it has a
|
||||
valid/non-expired Supervisor-issued access token. One way to do this would be for the CLI to invoke its own
|
||||
"pinniped oidc login" command in a subprocess, perhaps by using the same code that kubectl
|
||||
would use to invoke it. Or it could invoke the implementation of that subcommand within the same process,
|
||||
which might be safer than starting a new process. Either way, it will need to change some of the options
|
||||
to this command compared to the options that were listed in the kubeconfig's credential exec spec:
|
||||
- It should remove the flag to enable the Concierge (if present), because it does
|
||||
not need an actual mTLS credential for the cluster.
|
||||
- It should add (or overwrite) the credential cache location flag to prevent credential caching
|
||||
(not session caching) so it does not overwrite the user's current credential in the
|
||||
user's credential cache (if they have one).
|
||||
- It should explicitly set the PINNIPED_UPSTREAM_IDENTITY_PROVIDER_FLOW env var for the subcommand to force it
|
||||
to use a web-based flow for login, in case the kubeconfig was intended for a CLI-based (non-interactive)
|
||||
login.
|
||||
- It should leave intact (or add) the CLI flag to request a different audience, because that's the only way to force
|
||||
the "pinniped login oidc" command to check the expiration date of the cached access token to make sure that
|
||||
it has not expired or is about to expire (otherwise, it checks the expiry of the cached ID token). Note that
|
||||
this is assuming that the related bug in the Pinniped CLI is fixed as outlined in
|
||||
[this PR](https://github.com/vmware-tanzu/pinniped/pull/1857).
|
||||
- It should take care to correctly handle unicode characters that might exist in flag values when
|
||||
invoking the subcommand, e.g. emojis in the upstream IDP name flag.
|
||||
As long as the subcommand is successful, its stdout results can be ignored, because we only care about
|
||||
what it cached in the session cache as a side effect.
|
||||
4. Now that the user is authenticated, the command will need to be able to construct a session cache key
|
||||
exactly the same way that the "pinniped login oidc" subcommand did it, so it can be sure that it will be
|
||||
looking up the same session in the cache.
|
||||
5. Next, it could open the session cache and read the access token from it. This access token should still be
|
||||
valid/non-expired because of the "pinniped login oidc" subcommand that just succeeded.
|
||||
6. Finally, it could call the new Supervisor API to add consent for the PAT by sending the access token
|
||||
for authentication and sending the SHA-256 hash of the PAT to add it to the allow list for the current user
|
||||
identified by the access token. Revoking consent for a specific PAT would be a similar request,
|
||||
and revoking consent for all PATs associated with the current user could work in a similar way
|
||||
(but without needing to submit any hashed value of a specific PAT).
|
||||
|
||||
#### Upgrades
|
||||
|
||||
All changes will be backwards-compatible additions.
|
||||
|
||||
#### Tests
|
||||
|
||||
All aspects of the new features will be unit tested. Appropriate integration tests will also be added.
|
||||
|
||||
#### New Dependencies
|
||||
|
||||
We may like to use Google's golang client package for GitHub to help us call the GitHub API, although this would not
|
||||
be strictly necessary since these are simple REST API calls. It may help us implement things like pagination.
|
||||
|
||||
#### Performance Considerations
|
||||
|
||||
None.
|
||||
|
||||
#### Observability Considerations
|
||||
|
||||
Follow our pre-existing standards for error messages, log messages, custom resource status, etc.
|
||||
|
||||
#### Security Considerations
|
||||
|
||||
Some GitHub-specific considerations are already discussed above. Otherwise, this fits into the existing design
|
||||
of Pinniped without changing any of its existing security considerations.
|
||||
|
||||
#### Usability Considerations
|
||||
|
||||
No significant changes for end users in terms of how they authenticate or use kubectl.
|
||||
|
||||
#### Documentation Considerations
|
||||
|
||||
We will add API docs for GitHubIdentityProvider, and add docs on the website for authenticating with GitHub.
|
||||
|
||||
### Other Approaches Considered
|
||||
|
||||
None.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should consent for a PAT work across all GitHubIdentityProviders in a FederationDomain,
|
||||
or just one specific GitHubIdentityProvider in that FederationDomain?
|
||||
- Would it be helpful to offer a `GET` endpoint to list current PAT consents? What would a user do with this
|
||||
information?
|
||||
- Should we also reduce the lifetime of the Supervisor-issued refresh tokens? This would be a signal to the client
|
||||
that the token is not going to work. Would this help the Pinniped CLI remove stale entries from the session
|
||||
cache file more quickly?
|
||||
- For the consent CLI commands, what if the kubeconfig's exec plugin is a path to a different CLI? It could be
|
||||
a different path to a different version of the Pinniped CLI, or it could be a different CLI entirely
|
||||
(like the `tanzu` CLI). Does this matter?
|
||||
- Are the three GitHub API endpoints that we intend to use different for GitHub Enterprise Server (on-prem)?
|
||||
Do they have different paths or different inputs and outputs? Need to check the GitHub docs.
|
||||
|
||||
## Answered Questions
|
||||
|
||||
- Does the GitHub Identity Provider need `additionalClaimMappings`? No. This feature was only added for
|
||||
OIDCIdentityProvider, and not yet added for other identity provider types. Please raise an issue in
|
||||
this repo if you need this feature.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
The maintainers will most likely implement this proposal, if it is accepted.
|
||||
|
||||
Community contributions to the effort would be welcomed. Contact the maintainers if you might wish to get involved.
|
||||
|
||||
Implementing browser-based authentication will happen first. It is simpler and is a pre-requisite for
|
||||
the PAT consent feature for CLI-based authentication.
|
||||
@@ -16,13 +16,6 @@
|
||||
<p class="position">Engineer</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bio">
|
||||
<div class="image"><img src="/img/ben-petersen.png" /></div>
|
||||
<div class="info">
|
||||
<p class="name">Ben Petersen</p>
|
||||
<p class="position">Engineer</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Contributing:</h3>
|
||||
<p>The Pinniped project team welcomes contributions from the community, please see the <a href="https://github.com/vmware-tanzu/pinniped/blob/main/CONTRIBUTING.md">contributor’s guide</a> for more information.</p>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
144
test/integration/ptls_fips_test.go
Normal file
144
test/integration/ptls_fips_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build fips_strict
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
"k8s.io/client-go/util/cert"
|
||||
|
||||
"go.pinniped.dev/internal/crypto/ptls"
|
||||
"go.pinniped.dev/internal/testutil/tlsserver"
|
||||
"go.pinniped.dev/test/testlib"
|
||||
)
|
||||
|
||||
// Note: Everything in this file is an integration test only because we do not support build tags on unit tests.
|
||||
// These are effectively unit tests for the ptls package when compiled in FIPS mode.
|
||||
|
||||
// TestFIPSCipherSuites_Parallel ensures that if the list of default FIPS cipher suites changes, then we will know.
|
||||
// If this test ever fails during a golang upgrade, then we may need to change which ciphers we are using in
|
||||
// the ptls package in FIPS mode.
|
||||
func TestFIPSCipherSuites_Parallel(t *testing.T) {
|
||||
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
|
||||
|
||||
server, ca := tlsserver.TestServerIPv4(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// use the default fips config which contains a hard coded list of cipher suites
|
||||
// that should be equal to the default list of fips cipher suites.
|
||||
// assert that the client hello response has the same tls config as this test server.
|
||||
tlsserver.AssertTLS(t, r, ptls.Default)
|
||||
}), tlsserver.RecordTLSHello)
|
||||
|
||||
pool, err := cert.NewPoolFromBytes(ca)
|
||||
require.NoError(t, err)
|
||||
// create a tls config that does not explicitly set cipher suites,
|
||||
// and therefore uses goboring's default fips ciphers.
|
||||
defaultConfig := &tls.Config{
|
||||
RootCAs: pool,
|
||||
NextProtos: ptls.Default(nil).NextProtos, // we do not care about field for this test, so just make it match
|
||||
}
|
||||
transport := http.Transport{
|
||||
TLSClientConfig: defaultConfig,
|
||||
ForceAttemptHTTP2: true,
|
||||
}
|
||||
// make a request against the test server, which will validate that the
|
||||
// tls config of the client without explicitly set ciphers
|
||||
// is the same as the tls config of the test server with explicitly
|
||||
// set ciphers from ptls.
|
||||
request, _ := http.NewRequest("GET", server.URL, nil)
|
||||
response, err := transport.RoundTrip(request)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
}
|
||||
|
||||
// Every profile should use the same cipher suites in FIPS mode, because FIPS requires these ciphers.
|
||||
// Please treat this as a read-only const.
|
||||
var expectedFIPSCipherSuites = []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
|
||||
func TestDefault_Parallel(t *testing.T) {
|
||||
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
|
||||
|
||||
aCertPool := x509.NewCertPool()
|
||||
|
||||
actual := ptls.Default(aCertPool)
|
||||
expected := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12, // goboring does not currently support TLS 1.3, so prevent its use
|
||||
CipherSuites: expectedFIPSCipherSuites,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
RootCAs: aCertPool,
|
||||
}
|
||||
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestDefaultLDAP_Parallel(t *testing.T) {
|
||||
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
|
||||
|
||||
aCertPool := x509.NewCertPool()
|
||||
|
||||
actual := ptls.DefaultLDAP(aCertPool)
|
||||
expected := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12, // goboring does not currently support TLS 1.3, so prevent its use
|
||||
CipherSuites: expectedFIPSCipherSuites,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
RootCAs: aCertPool,
|
||||
}
|
||||
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestSecure_Parallel(t *testing.T) {
|
||||
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
|
||||
|
||||
aCertPool := x509.NewCertPool()
|
||||
|
||||
actual := ptls.Secure(aCertPool)
|
||||
expected := &tls.Config{
|
||||
// goboring does not currently support TLS 1.3, so where we would normally require it by making it the
|
||||
// min version for the secure profile, we cannot do that in FIPS mode
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12, // goboring does not currently support TLS 1.3, so prevent its use
|
||||
CipherSuites: expectedFIPSCipherSuites,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
RootCAs: aCertPool,
|
||||
}
|
||||
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestSecureServing_Parallel(t *testing.T) {
|
||||
_ = testlib.IntegrationEnv(t) // this function call is required for integration tests
|
||||
|
||||
opts := &options.SecureServingOptionsWithLoopback{SecureServingOptions: &options.SecureServingOptions{}}
|
||||
ptls.SecureServing(opts)
|
||||
|
||||
expectedFIPSCipherSuiteNames := make([]string, len(expectedFIPSCipherSuites))
|
||||
for i, suite := range expectedFIPSCipherSuites {
|
||||
expectedFIPSCipherSuiteNames[i] = tls.CipherSuiteName(suite)
|
||||
}
|
||||
|
||||
require.Equal(t, options.SecureServingOptionsWithLoopback{
|
||||
SecureServingOptions: &options.SecureServingOptions{
|
||||
CipherSuites: expectedFIPSCipherSuiteNames,
|
||||
// goboring does not currently support TLS 1.3, so where we would normally require it by making it the
|
||||
// min version for secure serving for aggregated API servers, we cannot do that in FIPS mode
|
||||
MinTLSVersion: "VersionTLS12",
|
||||
},
|
||||
}, *opts)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build fips_strict
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/client-go/util/cert"
|
||||
|
||||
"go.pinniped.dev/internal/crypto/ptls"
|
||||
"go.pinniped.dev/internal/testutil/tlsserver"
|
||||
"go.pinniped.dev/test/testlib"
|
||||
)
|
||||
|
||||
// TestFIPSCipherSuites_Parallel ensures that if the list of default fips cipher suites changes,
|
||||
// we will know. This is an integration test because we do not support build tags on unit tests.
|
||||
func TestFIPSCipherSuites_Parallel(t *testing.T) {
|
||||
_ = testlib.IntegrationEnv(t)
|
||||
|
||||
server, ca := tlsserver.TestServerIPv4(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// use the default fips config which contains a hard coded list of cipher suites
|
||||
// that should be equal to the default list of fips cipher suites.
|
||||
// assert that the client hello response has the same tls config as this test server.
|
||||
tlsserver.AssertTLS(t, r, ptls.Default)
|
||||
}), tlsserver.RecordTLSHello)
|
||||
|
||||
pool, err := cert.NewPoolFromBytes(ca)
|
||||
require.NoError(t, err)
|
||||
// create a tls config that does not explicitly set cipher suites,
|
||||
// and therefore uses goboring's default fips ciphers.
|
||||
defaultConfig := &tls.Config{
|
||||
RootCAs: pool,
|
||||
NextProtos: ptls.Default(nil).NextProtos, // we do not care about field for this test, so just make it match
|
||||
}
|
||||
transport := http.Transport{
|
||||
TLSClientConfig: defaultConfig,
|
||||
ForceAttemptHTTP2: true,
|
||||
}
|
||||
// make a request against the test server, which will validate that the
|
||||
// tls config of the client without explicitly set ciphers
|
||||
// is the same as the tls config of the test server with explicitly
|
||||
// set ciphers from ptls.
|
||||
request, _ := http.NewRequest("GET", server.URL, nil)
|
||||
response, err := transport.RoundTrip(request)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
}
|
||||
Reference in New Issue
Block a user