-
Notifications
You must be signed in to change notification settings - Fork 225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
backend(oidc): Add support for custom CA and skipping TLS verification #2880
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import ( | |
"compress/gzip" | ||
"context" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"encoding/base64" | ||
"encoding/json" | ||
"errors" | ||
|
@@ -54,6 +55,8 @@ type HeadlampConfig struct { | |
oidcClientID string | ||
oidcClientSecret string | ||
oidcIdpIssuerURL string | ||
oidcSkipTLSVerify bool | ||
oidcCACert string | ||
baseURL string | ||
oidcScopes []string | ||
proxyURLs []string | ||
|
@@ -366,7 +369,7 @@ func createHeadlampHandler(config *HeadlampConfig) http.Handler { | |
if config.useInCluster { | ||
context, err := kubeconfig.GetInClusterContext(config.oidcIdpIssuerURL, | ||
config.oidcClientID, config.oidcClientSecret, | ||
strings.Join(config.oidcScopes, ",")) | ||
strings.Join(config.oidcScopes, ","), config.oidcSkipTLSVerify, config.oidcCACert) | ||
if err != nil { | ||
logger.Log(logger.LevelError, nil, err, "Failed to get in-cluster context") | ||
} | ||
|
@@ -545,6 +548,32 @@ func createHeadlampHandler(config *HeadlampConfig) http.Handler { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
if oidcAuthConfig.SkipTLSVerify { | ||
tr := &http.Transport{ | ||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec | ||
} | ||
insecureClient := &http.Client{Transport: tr} | ||
ctx = oidc.ClientContext(ctx, insecureClient) | ||
} | ||
|
||
if oidcAuthConfig.CACert != "" { | ||
caCertPool := x509.NewCertPool() | ||
if !caCertPool.AppendCertsFromPEM([]byte(oidcAuthConfig.CACert)) { | ||
logger.Log(logger.LevelError, map[string]string{"oidc-ca-file": oidcAuthConfig.CACert}, | ||
errors.New("failed to append oidc-ca-file to cert pool"), "failed to append oidc-ca-file to cert pool") | ||
http.Error(w, "invalid oidc-ca-file", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
secureTLSClient := &http.Client{ | ||
Transport: &http.Transport{ | ||
TLSClientConfig: &tls.Config{RootCAs: caCertPool}, //nolint:gosec | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please leave a justification in a comment for why gosec was disabled here? |
||
}, | ||
} | ||
ctx = oidc.ClientContext(ctx, secureTLSClient) | ||
} | ||
|
||
provider, err := oidc.NewProvider(ctx, oidcAuthConfig.IdpIssuerURL) | ||
if err != nil { | ||
logger.Log(logger.LevelError, map[string]string{"idpIssuerURL": oidcAuthConfig.IdpIssuerURL}, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,7 +36,7 @@ func main() { | |
kubeConfigStore := kubeconfig.NewContextStore() | ||
multiplexer := NewMultiplexer(kubeConfigStore) | ||
|
||
StartHeadlampServer(&HeadlampConfig{ | ||
headlampConfig := &HeadlampConfig{ | ||
useInCluster: conf.InCluster, | ||
kubeConfigPath: conf.KubeConfigPath, | ||
port: conf.Port, | ||
|
@@ -48,12 +48,25 @@ func main() { | |
oidcClientSecret: conf.OidcClientSecret, | ||
oidcIdpIssuerURL: conf.OidcIdpIssuerURL, | ||
oidcScopes: strings.Split(conf.OidcScopes, ","), | ||
oidcSkipTLSVerify: conf.OidcSkipTLSVerify, | ||
baseURL: conf.BaseURL, | ||
proxyURLs: strings.Split(conf.ProxyURLs, ","), | ||
enableHelm: conf.EnableHelm, | ||
enableDynamicClusters: conf.EnableDynamicClusters, | ||
cache: cache, | ||
kubeConfigStore: kubeConfigStore, | ||
multiplexer: multiplexer, | ||
}) | ||
} | ||
|
||
if conf.OidcCAFile != "" { | ||
caFile, err := os.ReadFile(conf.OidcCAFile) | ||
if err != nil { | ||
logger.Log(logger.LevelError, nil, err, "reading oidc-ca-file") | ||
os.Exit(1) | ||
} | ||
|
||
headlampConfig.oidcCACert = string(caFile) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
StartHeadlampServer(headlampConfig) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package config | ||
|
||
import ( | ||
"crypto/x509" | ||
"errors" | ||
"flag" | ||
"fmt" | ||
|
@@ -35,6 +36,8 @@ type Config struct { | |
OidcClientSecret string `koanf:"oidc-client-secret"` | ||
OidcIdpIssuerURL string `koanf:"oidc-idp-issuer-url"` | ||
OidcScopes string `koanf:"oidc-scopes"` | ||
OidcSkipTLSVerify bool `koanf:"oidc-skip-tls-verify"` | ||
OidcCAFile string `koanf:"oidc-ca-file"` | ||
} | ||
|
||
func (c *Config) Validate() error { | ||
|
@@ -141,6 +144,22 @@ func Parse(args []string) (*Config, error) { | |
} | ||
} | ||
|
||
if config.OidcSkipTLSVerify { | ||
logger.Log(logger.LevelWarn, nil, nil, "oidc-skip-tls-verify is set, this is not safe for production") | ||
} | ||
|
||
if config.OidcCAFile != "" { | ||
caFile, err := os.ReadFile(config.OidcCAFile) | ||
if err != nil { | ||
return nil, fmt.Errorf("error reading oidc-ca-file: %w", err) | ||
} | ||
// check if the file is a valid PEM file | ||
caCertPool := x509.NewCertPool() | ||
if !caCertPool.AppendCertsFromPEM(caFile) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please rename the caFile to caFileContents? Currently it shares the same convention as the config.OidcCAFile... which lead me to believe from just reading this part that it was a filename. It might also be worth renaming config.OidcCAFile to config.OidcCAFilePath to be more explicit. |
||
return nil, errors.New("invalid oidc-ca-file") | ||
} | ||
} | ||
|
||
config.KubeConfigPath = kubeConfigPath | ||
|
||
return &config, nil | ||
|
@@ -166,6 +185,8 @@ func flagset() *flag.FlagSet { | |
f.String("oidc-idp-issuer-url", "", "Identity provider issuer URL for OIDC") | ||
f.String("oidc-scopes", "profile,email", | ||
"A comma separated list of scopes needed from the OIDC provider") | ||
f.Bool("oidc-skip-tls-verify", false, "Skip TLS verification for OIDC") | ||
f.String("oidc-ca-file", "", "CA file for OIDC") | ||
|
||
return f | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,10 +48,12 @@ type Context struct { | |
} | ||
|
||
type OidcConfig struct { | ||
ClientID string | ||
ClientSecret string | ||
IdpIssuerURL string | ||
Scopes []string | ||
ClientID string | ||
ClientSecret string | ||
IdpIssuerURL string | ||
Scopes []string | ||
SkipTLSVerify bool | ||
CACert string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please document this field? |
||
} | ||
|
||
// CustomObject represents the custom object that holds the HeadlampInfo regarding custom name. | ||
|
@@ -243,11 +245,25 @@ func (c *Context) OidcConfig() (*OidcConfig, error) { | |
return nil, errors.New("authProvider is nil") | ||
} | ||
|
||
var caCert string | ||
|
||
caFilePath, ok := c.AuthInfo.AuthProvider.Config["idp-certificate-authority"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please explain what this is in a comment? |
||
if ok { | ||
caFile, err := os.ReadFile(caFilePath) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder where caFilePath comes from and if the path needs to be validated? |
||
if err != nil { | ||
return nil, fmt.Errorf("failed to read idp-ca-file: %w", err) | ||
} | ||
|
||
caCert = string(caFile) | ||
} | ||
|
||
return &OidcConfig{ | ||
ClientID: c.AuthInfo.AuthProvider.Config["client-id"], | ||
ClientSecret: c.AuthInfo.AuthProvider.Config["client-secret"], | ||
Scopes: strings.Split(c.AuthInfo.AuthProvider.Config["scope"], ","), | ||
IdpIssuerURL: c.AuthInfo.AuthProvider.Config["idp-issuer-url"], | ||
ClientID: c.AuthInfo.AuthProvider.Config["client-id"], | ||
ClientSecret: c.AuthInfo.AuthProvider.Config["client-secret"], | ||
Scopes: strings.Split(c.AuthInfo.AuthProvider.Config["scope"], ","), | ||
IdpIssuerURL: c.AuthInfo.AuthProvider.Config["idp-issuer-url"], | ||
CACert: caCert, | ||
SkipTLSVerify: c.AuthInfo.AuthProvider.Config["insecure-skip-tls-verify"] == "true", | ||
}, nil | ||
} | ||
|
||
|
@@ -868,6 +884,8 @@ func splitKubeConfigPath(path string) []string { | |
func GetInClusterContext(oidcIssuerURL string, | ||
oidcClientID string, oidcClientSecret string, | ||
oidcScopes string, | ||
oidcSkipTLSVerify bool, | ||
oidcCACert string, | ||
) (*Context, error) { | ||
clusterConfig, err := rest.InClusterConfig() | ||
if err != nil { | ||
|
@@ -891,10 +909,12 @@ func GetInClusterContext(oidcIssuerURL string, | |
|
||
if oidcClientID != "" && oidcClientSecret != "" && oidcIssuerURL != "" && oidcScopes != "" { | ||
oidcConf = &OidcConfig{ | ||
ClientID: oidcClientID, | ||
ClientSecret: oidcClientSecret, | ||
IdpIssuerURL: oidcIssuerURL, | ||
Scopes: strings.Split(oidcScopes, ","), | ||
ClientID: oidcClientID, | ||
ClientSecret: oidcClientSecret, | ||
IdpIssuerURL: oidcIssuerURL, | ||
Scopes: strings.Split(oidcScopes, ","), | ||
SkipTLSVerify: oidcSkipTLSVerify, | ||
CACert: oidcCACert, | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please leave a justification in a comment for why gosec was disabled here?