diff --git a/Dockerfile b/Dockerfile index 0b0fd4675..73361880f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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. diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 5026925d6..3d6afae60 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -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) | diff --git a/README.md b/README.md index 07d9a847f..63fe36335 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/ROADMAP.md b/ROADMAP.md index 440ddcf97..1088fc1a7 100644 --- a/ROADMAP.md +++ b/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| diff --git a/SCOPE.md b/SCOPE.md index 91dff3bd2..b5b2b5bc4 100644 --- a/SCOPE.md +++ b/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 \ No newline at end of file diff --git a/go.mod b/go.mod index bb505a52c..5ac342659 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 20982c19a..1dcb3c584 100644 --- a/go.sum +++ b/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= diff --git a/hack/Dockerfile_fips b/hack/Dockerfile_fips index 3ae2f78eb..eb498ba65 100644 --- a/hack/Dockerfile_fips +++ b/hack/Dockerfile_fips @@ -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 diff --git a/internal/crypto/ptls/default.go b/internal/crypto/ptls/default.go deleted file mode 100644 index 98a700155..000000000 --- a/internal/crypto/ptls/default.go +++ /dev/null @@ -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 -} diff --git a/internal/crypto/ptls/profiles.go b/internal/crypto/ptls/profiles.go new file mode 100644 index 000000000..1adb6a4b6 --- /dev/null +++ b/internal/crypto/ptls/profiles.go @@ -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 +} diff --git a/internal/crypto/ptls/fips_strict.go b/internal/crypto/ptls/profiles_fips_strict.go similarity index 74% rename from internal/crypto/ptls/fips_strict.go rename to internal/crypto/ptls/profiles_fips_strict.go index b040bb12e..03da3b5b7 100644 --- a/internal/crypto/ptls/fips_strict.go +++ b/internal/crypto/ptls/profiles_fips_strict.go @@ -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) } diff --git a/internal/crypto/ptls/profiles_fips_strict_test.go b/internal/crypto/ptls/profiles_fips_strict_test.go new file mode 100644 index 000000000..d86699150 --- /dev/null +++ b/internal/crypto/ptls/profiles_fips_strict_test.go @@ -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. diff --git a/internal/crypto/ptls/profiles_test.go b/internal/crypto/ptls/profiles_test.go new file mode 100644 index 000000000..ac175c06b --- /dev/null +++ b/internal/crypto/ptls/profiles_test.go @@ -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) +} diff --git a/internal/crypto/ptls/ptls.go b/internal/crypto/ptls/ptls.go index bf2ae4e1f..6ebd86e02 100644 --- a/internal/crypto/ptls/ptls.go +++ b/internal/crypto/ptls/ptls.go @@ -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) } diff --git a/internal/crypto/ptls/ptls_test.go b/internal/crypto/ptls/ptls_test.go index 789b30fbf..e3eff9935 100644 --- a/internal/crypto/ptls/ptls_test.go +++ b/internal/crypto/ptls/ptls_test.go @@ -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() diff --git a/internal/crypto/ptls/secure.go b/internal/crypto/ptls/secure.go deleted file mode 100644 index d08139443..000000000 --- a/internal/crypto/ptls/secure.go +++ /dev/null @@ -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 -} diff --git a/proposals/1406_multiple-idps/README.md b/proposals/1406_multiple-idps/README.md index 11b973a92..ec368da67 100644 --- a/proposals/1406_multiple-idps/README.md +++ b/proposals/1406_multiple-idps/README.md @@ -1,7 +1,7 @@ --- title: "Multiple Identity Providers" authors: [ "@cfryanr" ] -status: "accepted" +status: "implemented" sponsor: [] approval_date: "July 12, 2023" --- diff --git a/proposals/1547_impersonation-proxy-external-certs/README.md b/proposals/1547_impersonation-proxy-external-certs/README.md index d67887021..3ba8a3b1b 100644 --- a/proposals/1547_impersonation-proxy-external-certs/README.md +++ b/proposals/1547_impersonation-proxy-external-certs/README.md @@ -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. diff --git a/proposals/1859_github-auth/README.md b/proposals/1859_github-auth/README.md new file mode 100644 index 000000000..c63a7052e --- /dev/null +++ b/proposals/1859_github-auth/README.md @@ -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: + clientSecret: +``` + +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:///v1alpha1/consent/githubpats` to add a new PAT +- `DELETE https:///v1alpha1/consent/githubpats/` + to remove a specific PAT +- `DELETE https:///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 ` to add a PAT to the consent list for the current user + for this FederationDomain +- `pinniped consent github-pat remove ` 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. diff --git a/site/themes/pinniped/layouts/partials/team.html b/site/themes/pinniped/layouts/partials/team.html index 3fdc4877d..f09027d51 100644 --- a/site/themes/pinniped/layouts/partials/team.html +++ b/site/themes/pinniped/layouts/partials/team.html @@ -16,13 +16,6 @@

Engineer

-
-
-
-

Ben Petersen

-

Engineer

-
-

Contributing:

The Pinniped project team welcomes contributions from the community, please see the contributor’s guide for more information.

diff --git a/site/themes/pinniped/static/img/ben-petersen.png b/site/themes/pinniped/static/img/ben-petersen.png deleted file mode 100644 index 871000817..000000000 Binary files a/site/themes/pinniped/static/img/ben-petersen.png and /dev/null differ diff --git a/test/integration/ptls_fips_test.go b/test/integration/ptls_fips_test.go new file mode 100644 index 000000000..fcea5fb6c --- /dev/null +++ b/test/integration/ptls_fips_test.go @@ -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) +} diff --git a/test/integration/securetls_fips_test.go b/test/integration/securetls_fips_test.go deleted file mode 100644 index 63a6d21a3..000000000 --- a/test/integration/securetls_fips_test.go +++ /dev/null @@ -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) -}