Add Upstreams options struct with validation

This commit is contained in:
Joel Speed
2020-07-05 10:21:05 +01:00
parent fb1bef2757
commit b6b5194190
4 changed files with 380 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
package validation
import (
"fmt"
"net/url"
"time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
)
func validateUpstreams(upstreams options.Upstreams) []string {
msgs := []string{}
ids := make(map[string]struct{})
paths := make(map[string]struct{})
for _, upstream := range upstreams {
msgs = append(msgs, validateUpstream(upstream, ids, paths)...)
}
return msgs
}
// validateUpstream validates that the upstream has valid options and that
// the ids and paths are unique across all options
func validateUpstream(upstream options.Upstream, ids, paths map[string]struct{}) []string {
msgs := []string{}
if upstream.ID == "" {
msgs = append(msgs, "upstream has empty id: ids are required for all upstreams")
}
if upstream.Path == "" {
msgs = append(msgs, fmt.Sprintf("upstream %q has empty path: paths are required for all upstreams", upstream.ID))
}
// Ensure upstream IDs are unique
if _, ok := ids[upstream.ID]; ok {
msgs = append(msgs, fmt.Sprintf("multiple upstreams found with id %q: upstream ids must be unique", upstream.ID))
}
ids[upstream.ID] = struct{}{}
// Ensure upstream Paths are unique
if _, ok := paths[upstream.Path]; ok {
msgs = append(msgs, fmt.Sprintf("multiple upstreams found with path %q: upstream paths must be unique", upstream.Path))
}
paths[upstream.Path] = struct{}{}
msgs = append(msgs, validateUpstreamURI(upstream)...)
msgs = append(msgs, validateStaticUpstream(upstream)...)
return msgs
}
// validateStaticUpstream checks that the StaticCode is only set when Static
// is set, and that any options that do not make sense for a static upstream
// are not set.
func validateStaticUpstream(upstream options.Upstream) []string {
msgs := []string{}
if !upstream.Static && upstream.StaticCode != nil {
msgs = append(msgs, fmt.Sprintf("upstream %q has staticCode (%d), but is not a static upstream, set 'static' for a static response", upstream.ID, *upstream.StaticCode))
}
// Checks after this only make sense when the upstream is static
if !upstream.Static {
return msgs
}
if upstream.URI != "" {
msgs = append(msgs, fmt.Sprintf("upstream %q has uri, but is a static upstream, this will have no effect.", upstream.ID))
}
if upstream.InsecureSkipTLSVerify {
msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID))
}
if upstream.FlushInterval != nil && *upstream.FlushInterval != time.Second {
msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID))
}
if !upstream.PassHostHeader {
msgs = append(msgs, fmt.Sprintf("upstream %q has passHostHeader, but is a static upstream, this will have no effect.", upstream.ID))
}
if !upstream.ProxyWebSockets {
msgs = append(msgs, fmt.Sprintf("upstream %q has proxyWebSockets, but is a static upstream, this will have no effect.", upstream.ID))
}
return msgs
}
func validateUpstreamURI(upstream options.Upstream) []string {
msgs := []string{}
if !upstream.Static && upstream.URI == "" {
msgs = append(msgs, fmt.Sprintf("upstream %q has empty uri: uris are required for all non-static upstreams", upstream.ID))
return msgs
}
// Checks after this only make sense the upstream is not static
if upstream.Static {
return msgs
}
u, err := url.Parse(upstream.URI)
if err != nil {
msgs = append(msgs, fmt.Sprintf("upstream %q has invalid uri: %v", upstream.ID, err))
return msgs
}
switch u.Scheme {
case "http", "https", "file":
// Valid, do nothing
default:
msgs = append(msgs, fmt.Sprintf("upstream %q has invalid scheme: %q", upstream.ID, u.Scheme))
}
return msgs
}
+191
View File
@@ -0,0 +1,191 @@
package validation
import (
"time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)
var _ = Describe("Upstreams", func() {
type validateUpstreamTableInput struct {
upstreams options.Upstreams
errStrings []string
}
flushInterval := 5 * time.Second
staticCode200 := 200
validHTTPUpstream := options.Upstream{
ID: "validHTTPUpstream",
Path: "/validHTTPUpstream",
URI: "http://localhost:8080",
}
validStaticUpstream := options.Upstream{
ID: "validStaticUpstream",
Path: "/validStaticUpstream",
Static: true,
PassHostHeader: true, // This would normally be defaulted
ProxyWebSockets: true, // this would normally be defaulted
}
validFileUpstream := options.Upstream{
ID: "validFileUpstream",
Path: "/validFileUpstream",
URI: "file://var/lib/foo",
}
emptyIDMsg := "upstream has empty id: ids are required for all upstreams"
emptyPathMsg := "upstream \"foo\" has empty path: paths are required for all upstreams"
emptyURIMsg := "upstream \"foo\" has empty uri: uris are required for all non-static upstreams"
invalidURIMsg := "upstream \"foo\" has invalid uri: parse \":\": missing protocol scheme"
invalidURISchemeMsg := "upstream \"foo\" has invalid scheme: \"ftp\""
staticWithURIMsg := "upstream \"foo\" has uri, but is a static upstream, this will have no effect."
staticWithInsecureMsg := "upstream \"foo\" has insecureSkipTLSVerify, but is a static upstream, this will have no effect."
staticWithFlushIntervalMsg := "upstream \"foo\" has flushInterval, but is a static upstream, this will have no effect."
staticWithPassHostHeaderMsg := "upstream \"foo\" has passHostHeader, but is a static upstream, this will have no effect."
staticWithProxyWebSocketsMsg := "upstream \"foo\" has proxyWebSockets, but is a static upstream, this will have no effect."
multipleIDsMsg := "multiple upstreams found with id \"foo\": upstream ids must be unique"
multiplePathsMsg := "multiple upstreams found with path \"/foo\": upstream paths must be unique"
staticCodeMsg := "upstream \"foo\" has staticCode (200), but is not a static upstream, set 'static' for a static response"
DescribeTable("validateUpstreams",
func(o *validateUpstreamTableInput) {
Expect(validateUpstreams(o.upstreams)).To(ConsistOf(o.errStrings))
},
Entry("with no upstreams", &validateUpstreamTableInput{
upstreams: options.Upstreams{},
errStrings: []string{},
}),
Entry("with valid upstreams", &validateUpstreamTableInput{
upstreams: options.Upstreams{
validHTTPUpstream,
validStaticUpstream,
validFileUpstream,
},
errStrings: []string{},
}),
Entry("with an empty ID", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "",
Path: "/foo",
URI: "http://localhost:8080",
},
},
errStrings: []string{emptyIDMsg},
}),
Entry("with an empty Path", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "foo",
Path: "",
URI: "http://localhost:8080",
},
},
errStrings: []string{emptyPathMsg},
}),
Entry("with an empty Path", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "foo",
Path: "",
URI: "http://localhost:8080",
},
},
errStrings: []string{emptyPathMsg},
}),
Entry("with an empty URI", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "foo",
Path: "/foo",
URI: "",
},
},
errStrings: []string{emptyURIMsg},
}),
Entry("with an invalid URI", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "foo",
Path: "/foo",
URI: ":",
},
},
errStrings: []string{invalidURIMsg},
}),
Entry("with an invalid URI scheme", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "foo",
Path: "/foo",
URI: "ftp://foo",
},
},
errStrings: []string{invalidURISchemeMsg},
}),
Entry("with a static upstream and invalid optons", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "foo",
Path: "/foo",
URI: "ftp://foo",
Static: true,
FlushInterval: &flushInterval,
PassHostHeader: false,
ProxyWebSockets: false,
InsecureSkipTLSVerify: true,
},
},
errStrings: []string{
staticWithURIMsg,
staticWithInsecureMsg,
staticWithFlushIntervalMsg,
staticWithPassHostHeaderMsg,
staticWithProxyWebSocketsMsg,
},
}),
Entry("with duplicate IDs", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "foo",
Path: "/foo1",
URI: "http://foo",
},
{
ID: "foo",
Path: "/foo2",
URI: "http://foo",
},
},
errStrings: []string{multipleIDsMsg},
}),
Entry("with duplicate Paths", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "foo1",
Path: "/foo",
URI: "http://foo",
},
{
ID: "foo2",
Path: "/foo",
URI: "http://foo",
},
},
errStrings: []string{multiplePathsMsg},
}),
Entry("when a static code is supplied without static", &validateUpstreamTableInput{
upstreams: options.Upstreams{
{
ID: "foo",
Path: "/foo",
StaticCode: &staticCode200,
},
},
errStrings: []string{emptyURIMsg, staticCodeMsg},
}),
)
})
+16
View File
@@ -0,0 +1,16 @@
package validation
import (
"testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestValidationSuite(t *testing.T) {
logger.SetOutput(GinkgoWriter)
RegisterFailHandler(Fail)
RunSpecs(t, "Validation Suite")
}