git.iko.soy/typeable/oauth2-proxy
on master help sign in
Issue: 2236 - adds an option to append CA certificates (#2237)

* adding append option for custom CA certs * updated test for changed GetCertPool signature, added testing to check functionality of empty and non-empty store * adding legacy options as well * update associated documentation * fixing code climate complaints - reduce number of return statements * Apply suggestions from code review Changes caFilesAppend (and variants) to useSystemTrustStore Co-authored-by: Jan Larwig <jan@larwig.com> * Apply suggestions from code review Fixes extra whitespaces and grammar. Co-authored-by: Koen van Zuijlen <8818390+kvanzuijlen@users.noreply.github.com> * fix indentation * update changelog --------- Co-authored-by: Jan Larwig <jan@larwig.com> Co-authored-by: Koen van Zuijlen <8818390+kvanzuijlen@users.noreply.github.com> Co-authored-by: Joel Speed <Joel.speed@hotmail.co.uk>

commita5006fd60623fda8d80f12f18172bc2d9acf1a8d authoremsixteeen <michael@kazenonline.com> dateWed 25 Oct 2023, 06:36 (3 years ago) parent601477a
8 files changed+82−27
CHANGELOG.md+1 docs/docs/configuration/alpha_config.md+1 docs/docs/configuration/overview.md+1 pkg/apis/options/legacy_options.go+3 pkg/apis/options/providers.go+3−1 pkg/util/util.go+31−2 pkg/util/util_test.go+41−23 pkg/validation/options.go+1−1
CHANGELOG.md+1
@@ -8,6 +8,7 @@
88
99## Changes since v7.5.1
1010
11+- [#2237](https://github.com/oauth2-proxy/oauth2-proxy/pull/2237) adds an option to append CA certificates (@emsixteeen)
1112- [#2128](https://github.com/oauth2-proxy/oauth2-proxy/pull/2128) Update dependencies (@vllvll)
1213- [#2274](https://github.com/oauth2-proxy/oauth2-proxy/pull/2274) Upgrade golang.org/x/net to v0.17.0 (@pierluigilenoci)
1314- [#2282](https://github.com/oauth2-proxy/oauth2-proxy/pull/2282) Fixed checking Google Groups membership using Google Application Credentials (@kvanzuijlen)
docs/docs/configuration/alpha_config.md+1
@@ -415,6 +415,7 @@ Provider holds all configuration for a single provider
415415| `provider` | _[ProviderType](#providertype)_ | Type is the OAuth provider<br/>must be set from the supported providers group,<br/>otherwise 'Google' is set as default |
416416| `name` | _string_ | Name is the providers display name<br/>if set, it will be shown to the users in the login page. |
417417| `caFiles` | _[]string_ | CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.<br/>If not specified, the default Go trust sources are used instead |
418+| `useSystemTrustStore` | _bool_ | UseSystemTrustStore determines if your custom CA files and the system trust store are used<br/>If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files. |
418419| `loginURL` | _string_ | LoginURL is the authentication endpoint |
419420| `loginURLParameters` | _[[]LoginURLParameter](#loginurlparameter)_ | LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL |
420421| `redeemURL` | _string_ | RedeemURL is the token redemption endpoint |
docs/docs/configuration/overview.md+1
@@ -154,6 +154,7 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/
154154| `--prompt` | string | [OIDC prompt](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest); if present, `approval-prompt` is ignored | `""` |
155155| `--provider` | string | OAuth provider | google |
156156| `--provider-ca-file` | string \| list | Paths to CA certificates that should be used when connecting to the provider. If not specified, the default Go trust sources are used instead. |
157+| `--use-system-trust-store` | bool | Determines if `provider-ca-file` files and the system trust store are used. If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files. | false |
157158| `--provider-display-name` | string | Override the provider's name with the given string; used for the sign-in page | (depends on provider) |
158159| `--ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` |
159160| `--ping-user-agent` | string | a User-Agent that can be used for basic health checks | `""` (don't check user agent) |
pkg/apis/options/legacy_options.go+3
@@ -507,6 +507,7 @@ type LegacyProvider struct {
507507 ProviderType string `flag:"provider" cfg:"provider"`
508508 ProviderName string `flag:"provider-display-name" cfg:"provider_display_name"`
509509 ProviderCAFiles []string `flag:"provider-ca-file" cfg:"provider_ca_files"`
510+ UseSystemTrustStore bool `flag:"use-system-trust-store" cfg:"use_system_trust_store"`
510511 OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url"`
511512 InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email"`
512513 InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification"`
@@ -561,6 +562,7 @@ func legacyProviderFlagSet() *pflag.FlagSet {
561562 flagSet.String("provider", "google", "OAuth provider")
562563 flagSet.String("provider-display-name", "", "Provider display name")
563564 flagSet.StringSlice("provider-ca-file", []string{}, "One or more paths to CA certificates that should be used when connecting to the provider. If not specified, the default Go trust sources are used instead.")
565+ flagSet.Bool("use-system-trust-store", false, "Determines if 'provider-ca-file' files and the system trust store are used. If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files.")
564566 flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
565567 flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
566568 flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL")
@@ -659,6 +661,7 @@ func (l *LegacyProvider) convert() (Providers, error) {
659661 ClientSecretFile: l.ClientSecretFile,
660662 Type: ProviderType(l.ProviderType),
661663 CAFiles: l.ProviderCAFiles,
664+ UseSystemTrustStore: l.UseSystemTrustStore,
662665 LoginURL: l.LoginURL,
663666 RedeemURL: l.RedeemURL,
664667 ProfileURL: l.ProfileURL,
pkg/apis/options/providers.go+3−1
@@ -59,7 +59,9 @@ type Provider struct {
5959 // CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
6060 // If not specified, the default Go trust sources are used instead
6161 CAFiles []string `json:"caFiles,omitempty"`
62−
62+ // UseSystemTrustStore determines if your custom CA files and the system trust store are used
63+ // If set to true, your custom CA files and the system trust store are used otherwise only your custom CA files.
64+ UseSystemTrustStore bool `json:"useSystemTrustStore,omitempty"`
6365 // LoginURL is the authentication endpoint
6466 LoginURL string `json:"loginURL,omitempty"`
6567 // LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL
pkg/util/util.go+31−2
@@ -14,11 +14,40 @@ import (
1414 "time"
1515)
1616
17−func GetCertPool(paths []string) (*x509.CertPool, error) {
17+func GetCertPool(paths []string, useSystemPool bool) (*x509.CertPool, error) {
1818 if len(paths) == 0 {
1919 return nil, fmt.Errorf("invalid empty list of Root CAs file paths")
2020 }
21− pool := x509.NewCertPool()
21+
22+ var pool *x509.CertPool
23+ if useSystemPool {
24+ rootPool, err := getSystemCertPool()
25+ if err != nil {
26+ return nil, fmt.Errorf("unable to get SystemCertPool when append is true - #{err}")
27+ }
28+ pool = rootPool
29+ } else {
30+ pool = x509.NewCertPool()
31+ }
32+
33+ return loadCertsFromPaths(paths, pool)
34+
35+}
36+
37+func getSystemCertPool() (*x509.CertPool, error) {
38+ rootPool, err := x509.SystemCertPool()
39+ if err != nil {
40+ return nil, err
41+ }
42+
43+ if rootPool == nil {
44+ return nil, fmt.Errorf("SystemCertPool is empty")
45+ }
46+
47+ return rootPool, nil
48+}
49+
50+func loadCertsFromPaths(paths []string, pool *x509.CertPool) (*x509.CertPool, error) {
2251 for _, path := range paths {
2352 // Cert paths are a configurable option
2453 data, err := os.ReadFile(path) // #nosec G304
pkg/util/util_test.go+41−23
@@ -190,7 +190,7 @@ func makeTestCertFile(t *testing.T, pem, dir string) *os.File {
190190}
191191
192192func TestGetCertPool_NoRoots(t *testing.T) {
193− _, err := GetCertPool([]string(nil))
193+ _, err := GetCertPool([]string(nil), false)
194194 assert.Error(t, err, "invalid empty list of Root CAs file paths")
195195}
196196
@@ -204,34 +204,52 @@ func TestGetCertPool(t *testing.T) {
204204 }
205205 }(tempDir)
206206
207+ rootPool, _ := x509.SystemCertPool()
208+ cleanPool := x509.NewCertPool()
209+
210+ tests := []struct {
211+ appendCerts bool
212+ pool *x509.CertPool
213+ }{
214+ {false, cleanPool},
215+ {true, rootPool},
216+ }
217+
207218 certFile1 := makeTestCertFile(t, root1Cert, tempDir)
208219 certFile2 := makeTestCertFile(t, root2Cert, tempDir)
209220
210− certPool, err := GetCertPool([]string{certFile1.Name(), certFile2.Name()})
211− assert.NoError(t, err)
221+ for _, tc := range tests {
222+ // Append certs to "known" pool so we can compare them
223+ assert.True(t, tc.pool.AppendCertsFromPEM([]byte(root1Cert)))
224+ assert.True(t, tc.pool.AppendCertsFromPEM([]byte(root2Cert)))
212225
213− cert1Block, _ := pem.Decode([]byte(cert1Cert))
214− cert1, _ := x509.ParseCertificate(cert1Block.Bytes)
215− assert.Equal(t, cert1.Subject.String(), cert1CertSubj)
226+ certPool, err := GetCertPool([]string{certFile1.Name(), certFile2.Name()}, tc.appendCerts)
227+ assert.NoError(t, err)
228+ assert.True(t, tc.pool.Equal(certPool))
216229
217− cert2Block, _ := pem.Decode([]byte(cert2Cert))
218− cert2, _ := x509.ParseCertificate(cert2Block.Bytes)
219− assert.Equal(t, cert2.Subject.String(), cert2CertSubj)
230+ cert1Block, _ := pem.Decode([]byte(cert1Cert))
231+ cert1, _ := x509.ParseCertificate(cert1Block.Bytes)
232+ assert.Equal(t, cert1.Subject.String(), cert1CertSubj)
220233
221− cert3Block, _ := pem.Decode([]byte(cert3Cert))
222− cert3, _ := x509.ParseCertificate(cert3Block.Bytes)
223− assert.Equal(t, cert3.Subject.String(), cert3CertSubj)
234+ cert2Block, _ := pem.Decode([]byte(cert2Cert))
235+ cert2, _ := x509.ParseCertificate(cert2Block.Bytes)
236+ assert.Equal(t, cert2.Subject.String(), cert2CertSubj)
224237
225− opts := x509.VerifyOptions{
226− Roots: certPool,
227− }
238+ cert3Block, _ := pem.Decode([]byte(cert3Cert))
239+ cert3, _ := x509.ParseCertificate(cert3Block.Bytes)
240+ assert.Equal(t, cert3.Subject.String(), cert3CertSubj)
241+
242+ opts := x509.VerifyOptions{
243+ Roots: certPool,
244+ }
228245
229− // "cert1" and "cert2" should be valid because "root1" and "root2" are in the certPool
230− // "cert3" should not be valid because "root3" is not in the certPool
231− _, err1 := cert1.Verify(opts)
232− assert.NoError(t, err1)
233− _, err2 := cert2.Verify(opts)
234− assert.NoError(t, err2)
235− _, err3 := cert3.Verify(opts)
236− assert.Error(t, err3)
246+ // "cert1" and "cert2" should be valid because "root1" and "root2" are in the certPool
247+ // "cert3" should not be valid because "root3" is not in the certPool
248+ _, err1 := cert1.Verify(opts)
249+ assert.NoError(t, err1)
250+ _, err2 := cert2.Verify(opts)
251+ assert.NoError(t, err2)
252+ _, err3 := cert3.Verify(opts)
253+ assert.Error(t, err3)
254+ }
237255}
pkg/validation/options.go+1−1
@@ -37,7 +37,7 @@ func Validate(o *options.Options) error {
3737 }
3838 http.DefaultClient = &http.Client{Transport: insecureTransport}
3939 } else if len(o.Providers[0].CAFiles) > 0 {
40− pool, err := util.GetCertPool(o.Providers[0].CAFiles)
40+ pool, err := util.GetCertPool(o.Providers[0].CAFiles, o.Providers[0].UseSystemTrustStore)
4141 if err == nil {
4242 transport := http.DefaultTransport.(*http.Transport).Clone()
4343 transport.TLSClientConfig = &tls.Config{