From b3b29ed433212930accd00ba4e7737913136523e Mon Sep 17 00:00:00 2001 From: MayorFaj Date: Sat, 14 Feb 2026 11:50:19 +0000 Subject: [PATCH] feat: add --config-test flag for validating configuration without starting the proxy Signed-off-by: MayorFaj --- .golangci.yml | 5 ++ docs/docs/configuration/alpha_config.md | 11 ++++ docs/docs/configuration/overview.md | 44 +++++++++++++-- main.go | 14 +++++ main_test.go | 75 +++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 4 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 31f4b033..2f4ee6ea 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -53,6 +53,11 @@ linters: - revive path: util/.*\.go$ text: "var-naming: avoid meaningless package names" + # pkg/version conflicts with go/version (added in Go 1.22) + - linters: + - revive + path: pkg/version/.*\.go$ + text: "var-naming: avoid package names that conflict with" - linters: - prealloc path: _test\.go diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index b4a75582..56bcba0c 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -106,6 +106,17 @@ the new config. oauth2-proxy --alpha-config ./path/to/new/config.yaml --config ./path/to/existing/config.cfg ``` +### Validating Alpha Configuration + +Use `--config-test` to validate your alpha configuration without starting the proxy: + +```bash +oauth2-proxy --config core.cfg --alpha-config alpha.yaml --config-test +``` + +This is useful for CI/CD pipelines to catch configuration errors before deployment. +See the [Configuration Validation](./overview.md#configuration-validation) section for more details. + ### How to use environment variables The alpha package supports the use of environment variables in place of yaml values, allowing sensitive data to be pulled from somewhere other than the yaml file. diff --git a/docs/docs/configuration/overview.md b/docs/docs/configuration/overview.md index 7bd7bf07..da4cf922 100644 --- a/docs/docs/configuration/overview.md +++ b/docs/docs/configuration/overview.md @@ -66,11 +66,47 @@ An example [oauth2-proxy.cfg](https://github.com/oauth2-proxy/oauth2-proxy/blob/ ### Command Line Options -| Flag | Description | -| ----------- | -------------------- | -| `--config` | path to config file | -| `--version` | print version string | +| Flag | Description | +| ---------------- | ------------------------------------------------------- | +| `--config` | path to config file | +| `--config-test` | test configuration and exit (for CI/CD validation) | +| `--version` | print version string | +## Configuration Validation + +The `--config-test` flag validates your configuration file without starting the proxy server. This is useful for: +- **CI/CD pipelines**: Pre-deployment validation +- **Configuration management**: Testing before applying changes +- **Debugging**: Verifying syntax and required fields + +### Usage + +```bash +# Test legacy config +oauth2-proxy --config /etc/oauth2-proxy.cfg --config-test + +# Test alpha config +oauth2-proxy --config /etc/core.cfg --alpha-config /etc/alpha.yaml --config-test + +# CI/CD pre-deployment check +oauth2-proxy --config new-config.cfg --config-test && deploy-to-production +``` + +### Exit Codes + +- **0**: Configuration is valid ✅ +- **1**: Configuration is invalid (errors printed to stderr) ❌ + +### Validation Coverage + +The `--config-test` flag performs the **same comprehensive validation** as normal startup, including: +- Required fields (client ID, client secret, cookie secret, etc.) +- Syntax validation (TOML/YAML parsing) +- Provider configuration +- Upstream server definitions +- Session store connectivity (e.g., Redis network checks if configured) + +**Note**: Cannot be combined with `--convert-config-to-alpha`. ### General Provider Options diff --git a/main.go b/main.go index 42e8bab0..ba970679 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ func main() { alphaConfig := configFlagSet.String("alpha-config", "", "path to alpha config file (use at your own risk - the structure in this config file may change between minor releases)") convertConfig := configFlagSet.Bool("convert-config-to-alpha", false, "if true, the proxy will load configuration as normal and convert existing configuration to the alpha config structure, and print it to stdout") showVersion := configFlagSet.Bool("version", false, "print version string") + configTest := configFlagSet.Bool("config-test", false, "test the configuration and exit") configFlagSet.Parse(os.Args[1:]) if *showVersion { @@ -37,11 +38,24 @@ func main() { logger.Fatal("cannot use alpha-config and convert-config-to-alpha together") } + if *configTest && *convertConfig { + logger.Fatal("cannot use config-test and convert-config-to-alpha together") + } + opts, err := loadConfiguration(*config, *alphaConfig, configFlagSet, os.Args[1:]) if err != nil { logger.Fatalf("ERROR: %v", err) } + if *configTest { + if err = validation.Validate(opts); err != nil { + logger.Errorf("%s", err) + os.Exit(1) + } + fmt.Println("configuration is valid") + return + } + if *convertConfig { if err := printConvertedConfig(opts); err != nil { logger.Fatalf("ERROR: could not convert config: %v", err) diff --git a/main_test.go b/main_test.go index cbe79683..52601db7 100644 --- a/main_test.go +++ b/main_test.go @@ -7,6 +7,7 @@ import ( "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" . "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options/testutil" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr" + "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/format" @@ -291,4 +292,78 @@ redirect_url="http://localhost:4180/oauth2/callback" expectedErr: errors.New("failed to load legacy options: failed to load config: error unmarshalling config: decoding failed due to the following error(s):\n\n'' has invalid keys: unknown_field"), }), ) + + Describe("Config Test Mode", func() { + const validConfig = ` +http_address="127.0.0.1:4180" +upstreams="http://httpbin" +client_id="oauth2-proxy" +client_secret="b2F1dGgyLXByb3h5LWNsaWVudC1zZWNyZXQK" +cookie_secret="OQINaROshtE9TcZkNAm-5Zs2Pv3xaWytBmc5W7sPX7w=" +email_domains="example.com" +cookie_secure="false" +redirect_url="http://localhost:4180/oauth2/callback" +` + + const invalidConfig = ` +http_address="127.0.0.1:4180" +upstreams="http://httpbin" +email_domains="example.com" +cookie_secure="false" +redirect_url="http://localhost:4180/oauth2/callback" +` + + writeTempConfig := func(content string) string { + file, err := os.CreateTemp("", "oauth2-proxy-test-config-XXXX.cfg") + Expect(err).ToNot(HaveOccurred()) + defer file.Close() + + _, err = file.WriteString(content) + Expect(err).ToNot(HaveOccurred()) + return file.Name() + } + + It("should pass validation with a valid configuration", func() { + configFile := writeTempConfig(validConfig) + defer os.Remove(configFile) + + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + opts, err := loadConfiguration(configFile, "", flagSet, []string{}) + Expect(err).ToNot(HaveOccurred()) + + err = validation.Validate(opts) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should fail validation with an invalid configuration (missing required fields)", func() { + configFile := writeTempConfig(invalidConfig) + defer os.Remove(configFile) + + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + opts, err := loadConfiguration(configFile, "", flagSet, []string{}) + Expect(err).ToNot(HaveOccurred()) + + err = validation.Validate(opts) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("invalid configuration")) + }) + + It("should fail to load a configuration file with syntax errors", func() { + configFile := writeTempConfig("this is not valid toml ===") + defer os.Remove(configFile) + + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + _, err := loadConfiguration(configFile, "", flagSet, []string{}) + Expect(err).To(HaveOccurred()) + }) + + It("should register the config-test flag", func() { + flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError) + flagSet.ParseErrorsAllowlist.UnknownFlags = true + configTest := flagSet.Bool("config-test", false, "test the configuration and exit") + err := flagSet.Parse([]string{"--config-test"}) + Expect(err).ToNot(HaveOccurred()) + Expect(*configTest).To(BeTrue()) + }) + }) })