git.iko.soy/typeable/oauth2-proxy
on master help sign in
Issue: 2236 - adds an option to append CA certificates (#2237)
a5006fdemsixteeen
CHANGELOG.md+1
@@ -8,6 +8,7 @@
8
8
9## Changes since v7.5.1
9## Changes since v7.5.1
10
10
11+- [#2237](https://github.com/oauth2-proxy/oauth2-proxy/pull/2237) adds an option to append CA certificates (@emsixteeen)
11- [#2128](https://github.com/oauth2-proxy/oauth2-proxy/pull/2128) Update dependencies (@vllvll)
12- [#2128](https://github.com/oauth2-proxy/oauth2-proxy/pull/2128) Update dependencies (@vllvll)
12- [#2274](https://github.com/oauth2-proxy/oauth2-proxy/pull/2274) Upgrade golang.org/x/net to v0.17.0 (@pierluigilenoci)
13- [#2274](https://github.com/oauth2-proxy/oauth2-proxy/pull/2274) Upgrade golang.org/x/net to v0.17.0 (@pierluigilenoci)
13- [#2282](https://github.com/oauth2-proxy/oauth2-proxy/pull/2282) Fixed checking Google Groups membership using Google Application Credentials (@kvanzuijlen)
14- [#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
415| `provider` | _[ProviderType](#providertype)_ | Type is the OAuth provider<br/>must be set from the supported providers group,<br/>otherwise 'Google' is set as default |
415| `provider` | _[ProviderType](#providertype)_ | Type is the OAuth provider<br/>must be set from the supported providers group,<br/>otherwise 'Google' is set as default |
416| `name` | _string_ | Name is the providers display name<br/>if set, it will be shown to the users in the login page. |
416| `name` | _string_ | Name is the providers display name<br/>if set, it will be shown to the users in the login page. |
417| `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 |
417| `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. |
418| `loginURL` | _string_ | LoginURL is the authentication endpoint |
419| `loginURL` | _string_ | LoginURL is the authentication endpoint |
419| `loginURLParameters` | _[[]LoginURLParameter](#loginurlparameter)_ | LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL |
420| `loginURLParameters` | _[[]LoginURLParameter](#loginurlparameter)_ | LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL |
420| `redeemURL` | _string_ | RedeemURL is the token redemption endpoint |
421| `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/
154| `--prompt` | string | [OIDC prompt](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest); if present, `approval-prompt` is ignored | `""` |
154| `--prompt` | string | [OIDC prompt](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest); if present, `approval-prompt` is ignored | `""` |
155| `--provider` | string | OAuth provider | google |
155| `--provider` | string | OAuth provider | google |
156| `--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. |
156| `--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 |
157| `--provider-display-name` | string | Override the provider's name with the given string; used for the sign-in page | (depends on provider) |
158| `--provider-display-name` | string | Override the provider's name with the given string; used for the sign-in page | (depends on provider) |
158| `--ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` |
159| `--ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` |
159| `--ping-user-agent` | string | a User-Agent that can be used for basic health checks | `""` (don't check user agent) |
160| `--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 {
507 ProviderType string `flag:"provider" cfg:"provider"`
507 ProviderType string `flag:"provider" cfg:"provider"`
508 ProviderName string `flag:"provider-display-name" cfg:"provider_display_name"`
508 ProviderName string `flag:"provider-display-name" cfg:"provider_display_name"`
509 ProviderCAFiles []string `flag:"provider-ca-file" cfg:"provider_ca_files"`
509 ProviderCAFiles []string `flag:"provider-ca-file" cfg:"provider_ca_files"`
510+ UseSystemTrustStore bool `flag:"use-system-trust-store" cfg:"use_system_trust_store"`
510 OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url"`
511 OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url"`
511 InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email"`
512 InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email"`
512 InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification"`
513 InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification"`
@@ -561,6 +562,7 @@ func legacyProviderFlagSet() *pflag.FlagSet {
561 flagSet.String("provider", "google", "OAuth provider")
562 flagSet.String("provider", "google", "OAuth provider")
562 flagSet.String("provider-display-name", "", "Provider display name")
563 flagSet.String("provider-display-name", "", "Provider display name")
563 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.")
564 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.")
564 flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
566 flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
565 flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
567 flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
566 flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL")
568 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) {
659 ClientSecretFile: l.ClientSecretFile,
661 ClientSecretFile: l.ClientSecretFile,
660 Type: ProviderType(l.ProviderType),
662 Type: ProviderType(l.ProviderType),
661 CAFiles: l.ProviderCAFiles,
663 CAFiles: l.ProviderCAFiles,
664+ UseSystemTrustStore: l.UseSystemTrustStore,
662 LoginURL: l.LoginURL,
665 LoginURL: l.LoginURL,
663 RedeemURL: l.RedeemURL,
666 RedeemURL: l.RedeemURL,
664 ProfileURL: l.ProfileURL,
667 ProfileURL: l.ProfileURL,
pkg/apis/options/providers.go+3−1
@@ -59,7 +59,9 @@ type Provider struct {
59 // CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
59 // CAFiles is a list of paths to CA certificates that should be used when connecting to the provider.
60 // If not specified, the default Go trust sources are used instead
60 // If not specified, the default Go trust sources are used instead
61 CAFiles []string `json:"caFiles,omitempty"`
61 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"`
63 // LoginURL is the authentication endpoint
65 // LoginURL is the authentication endpoint
64 LoginURL string `json:"loginURL,omitempty"`
66 LoginURL string `json:"loginURL,omitempty"`
65 // LoginURLParameters defines the parameters that can be passed from the start URL to the IdP login URL
67 // 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 (
14 "time"
14 "time"
15)
15)
16
16
17−func GetCertPool(paths []string) (*x509.CertPool, error) {
17+func GetCertPool(paths []string, useSystemPool bool) (*x509.CertPool, error) {
18 if len(paths) == 0 {
18 if len(paths) == 0 {
19 return nil, fmt.Errorf("invalid empty list of Root CAs file paths")
19 return nil, fmt.Errorf("invalid empty list of Root CAs file paths")
20 }
20 }
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 {
21− pool := x509.NewCertPool()
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) {
22 for _, path := range paths {
51 for _, path := range paths {
23 // Cert paths are a configurable option
52 // Cert paths are a configurable option
24 data, err := os.ReadFile(path) // #nosec G304
53 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 {
190}
190}
191
191
192func TestGetCertPool_NoRoots(t *testing.T) {
192func TestGetCertPool_NoRoots(t *testing.T) {
193− _, err := GetCertPool([]string(nil))
193+ _, err := GetCertPool([]string(nil), false)
194 assert.Error(t, err, "invalid empty list of Root CAs file paths")
194 assert.Error(t, err, "invalid empty list of Root CAs file paths")
195}
195}
196
196
@@ -204,34 +204,52 @@ func TestGetCertPool(t *testing.T) {
204 }
204 }
205 }(tempDir)
205 }(tempDir)
206
206
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+
207 certFile1 := makeTestCertFile(t, root1Cert, tempDir)
218 certFile1 := makeTestCertFile(t, root1Cert, tempDir)
208 certFile2 := makeTestCertFile(t, root2Cert, tempDir)
219 certFile2 := makeTestCertFile(t, root2Cert, tempDir)
209
220
210− certPool, err := GetCertPool([]string{certFile1.Name(), certFile2.Name()})
221+ for _, tc := range tests {
211− assert.NoError(t, err)
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)))
212
225
213− cert1Block, _ := pem.Decode([]byte(cert1Cert))
226+ certPool, err := GetCertPool([]string{certFile1.Name(), certFile2.Name()}, tc.appendCerts)
214− cert1, _ := x509.ParseCertificate(cert1Block.Bytes)
227+ assert.NoError(t, err)
215− assert.Equal(t, cert1.Subject.String(), cert1CertSubj)
228+ assert.True(t, tc.pool.Equal(certPool))
216
229
217− cert2Block, _ := pem.Decode([]byte(cert2Cert))
230+ cert1Block, _ := pem.Decode([]byte(cert1Cert))
218− cert2, _ := x509.ParseCertificate(cert2Block.Bytes)
231+ cert1, _ := x509.ParseCertificate(cert1Block.Bytes)
219− assert.Equal(t, cert2.Subject.String(), cert2CertSubj)
232+ assert.Equal(t, cert1.Subject.String(), cert1CertSubj)
220
233
221− cert3Block, _ := pem.Decode([]byte(cert3Cert))
234+ cert2Block, _ := pem.Decode([]byte(cert2Cert))
222− cert3, _ := x509.ParseCertificate(cert3Block.Bytes)
235+ cert2, _ := x509.ParseCertificate(cert2Block.Bytes)
223− assert.Equal(t, cert3.Subject.String(), cert3CertSubj)
236+ assert.Equal(t, cert2.Subject.String(), cert2CertSubj)
224
237
238+ cert3Block, _ := pem.Decode([]byte(cert3Cert))
239+ cert3, _ := x509.ParseCertificate(cert3Block.Bytes)
240+ assert.Equal(t, cert3.Subject.String(), cert3CertSubj)
241+
225− opts := x509.VerifyOptions{
242+ opts := x509.VerifyOptions{
226− Roots: certPool,
243+ Roots: certPool,
227− }
244+ }
228
245
229− // "cert1" and "cert2" should be valid because "root1" and "root2" are in the certPool
246+ // "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
247+ // "cert3" should not be valid because "root3" is not in the certPool
231− _, err1 := cert1.Verify(opts)
248+ _, err1 := cert1.Verify(opts)
232− assert.NoError(t, err1)
249+ assert.NoError(t, err1)
233− _, err2 := cert2.Verify(opts)
250+ _, err2 := cert2.Verify(opts)
234− assert.NoError(t, err2)
251+ assert.NoError(t, err2)
235− _, err3 := cert3.Verify(opts)
252+ _, err3 := cert3.Verify(opts)
236− assert.Error(t, err3)
253+ assert.Error(t, err3)
254+ }
237}
255}
pkg/validation/options.go+1−1
@@ -37,7 +37,7 @@ func Validate(o *options.Options) error {
37 }
37 }
38 http.DefaultClient = &http.Client{Transport: insecureTransport}
38 http.DefaultClient = &http.Client{Transport: insecureTransport}
39 } else if len(o.Providers[0].CAFiles) > 0 {
39 } 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)
41 if err == nil {
41 if err == nil {
42 transport := http.DefaultTransport.(*http.Transport).Clone()
42 transport := http.DefaultTransport.(*http.Transport).Clone()
43 transport.TLSClientConfig = &tls.Config{
43 transport.TLSClientConfig = &tls.Config{