Fix golangci-lint errors (#10196)

* Fix golangci-lint errors

Signed-off-by: z1cheng <imchench@gmail.com>

* Fix dupl errors

Signed-off-by: z1cheng <imchench@gmail.com>

* Fix comments

Signed-off-by: z1cheng <imchench@gmail.com>

* Fix errcheck lint errors

Signed-off-by: z1cheng <imchench@gmail.com>

* Fix assert in e2e test

Signed-off-by: z1cheng <imchench@gmail.com>

* Not interrupt the waitForPodsReady

Signed-off-by: z1cheng <imchench@gmail.com>

* Replace string with constant

Signed-off-by: z1cheng <imchench@gmail.com>

* Fix comments

Signed-off-by: z1cheng <imchench@gmail.com>

* Revert write file permision

Signed-off-by: z1cheng <imchench@gmail.com>

---------

Signed-off-by: z1cheng <imchench@gmail.com>
This commit is contained in:
Chen Chen 2023-08-31 15:36:48 +08:00 committed by GitHub
parent 46d87d3462
commit b3060bfbd0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
253 changed files with 2434 additions and 2113 deletions

View file

@ -18,11 +18,12 @@ package main
import ( import (
"fmt" "fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"net/http" "net/http"
"os" "os"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/ingress-nginx/internal/ingress/controller" "k8s.io/ingress-nginx/internal/ingress/controller"

View file

@ -114,7 +114,6 @@ func main() {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
} }
func backendsAll() { func backendsAll() {
@ -155,10 +154,16 @@ func backendsList() {
fmt.Println(unmarshalErr) fmt.Println(unmarshalErr)
return return
} }
backends := f.([]interface{}) backends, ok := f.([]interface{})
if !ok {
fmt.Printf("unexpected type: %T", f)
}
for _, backendi := range backends { for _, backendi := range backends {
backend := backendi.(map[string]interface{}) backend, ok := backendi.(map[string]interface{})
if !ok {
fmt.Printf("unexpected type: %T", backendi)
}
fmt.Println(backend["name"].(string)) fmt.Println(backend["name"].(string))
} }
} }
@ -180,12 +185,22 @@ func backendsGet(name string) {
fmt.Println(unmarshalErr) fmt.Println(unmarshalErr)
return return
} }
backends := f.([]interface{}) backends, ok := f.([]interface{})
if !ok {
fmt.Printf("unexpected type: %T", f)
}
for _, backendi := range backends { for _, backendi := range backends {
backend := backendi.(map[string]interface{}) backend, ok := backendi.(map[string]interface{})
if !ok {
fmt.Printf("unexpected type: %T", backendi)
}
if backend["name"].(string) == name { if backend["name"].(string) == name {
printed, _ := json.MarshalIndent(backend, "", " ") printed, err := json.MarshalIndent(backend, "", " ")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(printed)) fmt.Println(string(printed))
return return
} }
@ -213,18 +228,7 @@ func certGet(host string) {
} }
func general() { func general() {
//TODO: refactor to obtain ingress-nginx pod count from the api server // TODO: refactor to obtain ingress-nginx pod count from the api server
/*
statusCode, body, requestErr := nginx.NewGetStatusRequest(generalPath)
if requestErr != nil {
fmt.Println(requestErr)
return
}
if statusCode != 200 {
fmt.Printf("Nginx returned code %v\n", statusCode)
return
}
*/
var prettyBuffer bytes.Buffer var prettyBuffer bytes.Buffer
indentErr := json.Indent(&prettyBuffer, []byte("{}"), "", " ") indentErr := json.Indent(&prettyBuffer, []byte("{}"), "", " ")

View file

@ -47,5 +47,4 @@ func logger(address string) {
server.Wait() server.Wait()
klog.Infof("Stopping logger") klog.Infof("Stopping logger")
} }

View file

@ -153,7 +153,6 @@ func main() {
if errExists == nil { if errExists == nil {
conf.IsChroot = true conf.IsChroot = true
go logger(conf.InternalLoggerAddress) go logger(conf.InternalLoggerAddress)
} }
go metrics.StartHTTPServer(conf.HealthCheckHost, conf.ListenPorts.Health, mux) go metrics.StartHTTPServer(conf.HealthCheckHost, conf.ListenPorts.Health, mux)
@ -282,10 +281,10 @@ func checkService(key string, kubeClient *kubernetes.Clientset) error {
} }
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
return fmt.Errorf("No service with name %v found in namespace %v: %v", name, ns, err) return fmt.Errorf("no service with name %v found in namespace %v: %v", name, ns, err)
} }
return fmt.Errorf("Unexpected error searching service with name %v in namespace %v: %v", name, ns, err) return fmt.Errorf("unexpected error searching service with name %v in namespace %v: %v", name, ns, err)
} }
return nil return nil

View file

@ -47,7 +47,7 @@ func TestCreateApiserverClient(t *testing.T) {
func init() { func init() {
// the default value of nginx.TemplatePath assumes the template exists in // the default value of nginx.TemplatePath assumes the template exists in
// the root filesystem and not in the rootfs directory // the root filesystem and not in the rootfs directory
path, err := filepath.Abs(filepath.Join("../../rootfs/", nginx.TemplatePath)) path, err := filepath.Abs(filepath.Join("..", "..", "rootfs", nginx.TemplatePath))
if err == nil { if err == nil {
nginx.TemplatePath = path nginx.TemplatePath = path
} }
@ -87,14 +87,14 @@ func TestHandleSigterm(t *testing.T) {
ingressflags.ResetForTesting(func() { t.Fatal("bad parse") }) ingressflags.ResetForTesting(func() { t.Fatal("bad parse") })
os.Setenv("POD_NAME", podName) t.Setenv("POD_NAME", podName)
os.Setenv("POD_NAMESPACE", namespace) t.Setenv("POD_NAMESPACE", namespace)
oldArgs := os.Args oldArgs := os.Args
defer func() { defer func() {
os.Setenv("POD_NAME", "") t.Setenv("POD_NAME", "")
os.Setenv("POD_NAMESPACE", "") t.Setenv("POD_NAMESPACE", "")
os.Args = oldArgs os.Args = oldArgs
}() }()

View file

@ -63,13 +63,14 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
return cmd return cmd
} }
func backends(flags *genericclioptions.ConfigFlags, podName string, deployment string, selector string, container string, backend string, onlyList bool) error { func backends(flags *genericclioptions.ConfigFlags, podName, deployment, selector, container, backend string, onlyList bool) error {
var command []string var command []string
if onlyList { switch {
case onlyList:
command = []string{"/dbg", "backends", "list"} command = []string{"/dbg", "backends", "list"}
} else if backend != "" { case backend != "":
command = []string{"/dbg", "backends", "get", backend} command = []string{"/dbg", "backends", "get", backend}
} else { default:
command = []string{"/dbg", "backends", "all"} command = []string{"/dbg", "backends", "all"}
} }

View file

@ -59,7 +59,7 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
return cmd return cmd
} }
func certs(flags *genericclioptions.ConfigFlags, podName string, deployment string, selector string, container string, host string) error { func certs(flags *genericclioptions.ConfigFlags, podName, deployment, selector, container, host string) error {
command := []string{"/dbg", "certs", "get", host} command := []string{"/dbg", "certs", "get", host}
pod, err := request.ChoosePod(flags, podName, deployment, selector) pod, err := request.ChoosePod(flags, podName, deployment, selector)

View file

@ -55,7 +55,7 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
return cmd return cmd
} }
func conf(flags *genericclioptions.ConfigFlags, host string, podName string, deployment string, selector string, container string) error { func conf(flags *genericclioptions.ConfigFlags, host, podName, deployment, selector, container string) error {
pod, err := request.ChoosePod(flags, podName, deployment, selector) pod, err := request.ChoosePod(flags, podName, deployment, selector)
if err != nil { if err != nil {
return err return err

View file

@ -55,7 +55,7 @@ type execFlags struct {
Stdin bool Stdin bool
} }
func exec(flags *genericclioptions.ConfigFlags, podName string, deployment string, selector string, container string, cmd []string, opts execFlags) error { func exec(flags *genericclioptions.ConfigFlags, podName, deployment, selector, container string, cmd []string, opts execFlags) error {
pod, err := request.ChoosePod(flags, podName, deployment, selector) pod, err := request.ChoosePod(flags, podName, deployment, selector)
if err != nil { if err != nil {
return err return err

View file

@ -47,7 +47,7 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
return cmd return cmd
} }
func general(flags *genericclioptions.ConfigFlags, podName string, deployment string, selector string, container string) error { func general(flags *genericclioptions.ConfigFlags, podName, deployment, selector, container string) error {
pod, err := request.ChoosePod(flags, podName, deployment, selector) pod, err := request.ChoosePod(flags, podName, deployment, selector)
if err != nil { if err != nil {
return err return err

View file

@ -74,9 +74,9 @@ func ingresses(flags *genericclioptions.ConfigFlags, host string, allNamespaces
if host != "" { if host != "" {
rowsWithHost := make([]ingressRow, 0) rowsWithHost := make([]ingressRow, 0)
for _, row := range rows { for i := range rows {
if row.Host == host { if rows[i].Host == host {
rowsWithHost = append(rowsWithHost, row) rowsWithHost = append(rowsWithHost, rows[i])
} }
} }
rows = rowsWithHost rows = rowsWithHost
@ -91,7 +91,8 @@ func ingresses(flags *genericclioptions.ConfigFlags, host string, allNamespaces
fmt.Fprintln(printer, "INGRESS NAME\tHOST+PATH\tADDRESSES\tTLS\tSERVICE\tSERVICE PORT\tENDPOINTS") fmt.Fprintln(printer, "INGRESS NAME\tHOST+PATH\tADDRESSES\tTLS\tSERVICE\tSERVICE PORT\tENDPOINTS")
} }
for _, row := range rows { for i := range rows {
row := &rows[i]
var tlsMsg string var tlsMsg string
if row.TLS { if row.TLS {
tlsMsg = "YES" tlsMsg = "YES"
@ -134,8 +135,8 @@ type ingressRow struct {
func getIngressRows(ingresses *[]networking.Ingress) []ingressRow { func getIngressRows(ingresses *[]networking.Ingress) []ingressRow {
rows := make([]ingressRow, 0) rows := make([]ingressRow, 0)
for _, ing := range *ingresses { for i := range *ingresses {
ing := &(*ingresses)[i]
address := "" address := ""
for _, lbIng := range ing.Status.LoadBalancer.Ingress { for _, lbIng := range ing.Status.LoadBalancer.Ingress {
if len(lbIng.IP) > 0 { if len(lbIng.IP) > 0 {
@ -182,7 +183,7 @@ func getIngressRows(ingresses *[]networking.Ingress) []ingressRow {
for _, rule := range ing.Spec.Rules { for _, rule := range ing.Spec.Rules {
_, hasTLS := tlsHosts[rule.Host] _, hasTLS := tlsHosts[rule.Host]
//Handle ingress with no paths // Handle ingress with no paths
if rule.HTTP == nil { if rule.HTTP == nil {
row := ingressRow{ row := ingressRow{
Namespace: ing.Namespace, Namespace: ing.Namespace,

View file

@ -24,7 +24,6 @@ import (
) )
func TestGetIngressInformation(t *testing.T) { func TestGetIngressInformation(t *testing.T) {
testcases := map[string]struct { testcases := map[string]struct {
ServiceBackend *networking.IngressServiceBackend ServiceBackend *networking.IngressServiceBackend
wantName string wantName string

View file

@ -111,11 +111,13 @@ type lintOptions struct {
} }
func (opts *lintOptions) Validate() error { func (opts *lintOptions) Validate() error {
//nolint:dogsled // Ignore 3 blank identifiers
_, _, _, err := util.ParseVersionString(opts.versionFrom) _, _, _, err := util.ParseVersionString(opts.versionFrom)
if err != nil { if err != nil {
return err return err
} }
//nolint:dogsled // Ignore 3 blank identifiers
_, _, _, err = util.ParseVersionString(opts.versionTo) _, _, _, err = util.ParseVersionString(opts.versionTo)
if err != nil { if err != nil {
return err return err
@ -131,9 +133,9 @@ type lint interface {
Version() string Version() string
} }
func checkObjectArray(lints []lint, objects []kmeta.Object, opts lintOptions) { func checkObjectArray(allLints []lint, objects []kmeta.Object, opts lintOptions) {
usedLints := make([]lint, 0) usedLints := make([]lint, 0)
for _, lint := range lints { for _, lint := range allLints {
lintVersion := lint.Version() lintVersion := lint.Version()
if lint.Version() == "" { if lint.Version() == "" {
lintVersion = "0.0.0" lintVersion = "0.0.0"
@ -189,7 +191,7 @@ func ingresses(opts lintOptions) error {
return err return err
} }
var iLints []lints.IngressLint = lints.GetIngressLints() iLints := lints.GetIngressLints()
genericLints := make([]lint, len(iLints)) genericLints := make([]lint, len(iLints))
for i := range iLints { for i := range iLints {
genericLints[i] = iLints[i] genericLints[i] = iLints[i]
@ -216,7 +218,7 @@ func deployments(opts lintOptions) error {
return err return err
} }
var iLints []lints.DeploymentLint = lints.GetDeploymentLints() iLints := lints.GetDeploymentLints()
genericLints := make([]lint, len(iLints)) genericLints := make([]lint, len(iLints))
for i := range iLints { for i := range iLints {
genericLints[i] = iLints[i] genericLints[i] = iLints[i]

View file

@ -95,7 +95,7 @@ func (o *logsFlags) toStrings() []string {
return r return r
} }
func logs(flags *genericclioptions.ConfigFlags, podName string, deployment string, selector string, container string, opts logsFlags) error { func logs(flags *genericclioptions.ConfigFlags, podName, deployment, selector, container string, opts logsFlags) error {
pod, err := request.ChoosePod(flags, podName, deployment, selector) pod, err := request.ChoosePod(flags, podName, deployment, selector)
if err != nil { if err != nil {
return err return err

View file

@ -45,7 +45,7 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
return cmd return cmd
} }
func ssh(flags *genericclioptions.ConfigFlags, podName string, deployment string, selector string, container string) error { func ssh(flags *genericclioptions.ConfigFlags, podName, deployment, selector, container string) error {
pod, err := request.ChoosePod(flags, podName, deployment, selector) pod, err := request.ChoosePod(flags, podName, deployment, selector)
if err != nil { if err != nil {
return err return err

View file

@ -38,11 +38,11 @@ func PodExecString(flags *genericclioptions.ConfigFlags, pod *apiv1.Pod, contain
// ExecToString runs a kubectl subcommand and returns stdout as a string // ExecToString runs a kubectl subcommand and returns stdout as a string
func ExecToString(flags *genericclioptions.ConfigFlags, args []string) (string, error) { func ExecToString(flags *genericclioptions.ConfigFlags, args []string) (string, error) {
kArgs := getKubectlConfigFlags(flags) kubectlArgs := getKubectlConfigFlags(flags)
kArgs = append(kArgs, args...) kubectlArgs = append(kubectlArgs, args...)
buf := bytes.NewBuffer(make([]byte, 0)) buf := bytes.NewBuffer(make([]byte, 0))
err := execToWriter(append([]string{"kubectl"}, kArgs...), buf) err := execToWriter(append([]string{"kubectl"}, kubectlArgs...), buf)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -51,9 +51,9 @@ func ExecToString(flags *genericclioptions.ConfigFlags, args []string) (string,
// Exec replaces the current process with a kubectl invocation // Exec replaces the current process with a kubectl invocation
func Exec(flags *genericclioptions.ConfigFlags, args []string) error { func Exec(flags *genericclioptions.ConfigFlags, args []string) error {
kArgs := getKubectlConfigFlags(flags) kubectlArgs := getKubectlConfigFlags(flags)
kArgs = append(kArgs, args...) kubectlArgs = append(kubectlArgs, args...)
return execCommand(append([]string{"kubectl"}, kArgs...)) return execCommand(append([]string{"kubectl"}, kubectlArgs...))
} }
// Replaces the currently running process with the given command // Replaces the currently running process with the given command
@ -70,6 +70,7 @@ func execCommand(args []string) error {
// Runs a command and returns stdout // Runs a command and returns stdout
func execToWriter(args []string, writer io.Writer) error { func execToWriter(args []string, writer io.Writer) error {
//nolint:gosec // Ignore G204 error
cmd := exec.Command(args[0], args[1:]...) cmd := exec.Command(args[0], args[1:]...)
op, err := cmd.StdoutPipe() op, err := cmd.StdoutPipe()
@ -78,7 +79,7 @@ func execToWriter(args []string, writer io.Writer) error {
} }
go func() { go func() {
io.Copy(writer, op) //nolint:errcheck io.Copy(writer, op) //nolint:errcheck // Ignore the error
}() }()
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
@ -106,7 +107,6 @@ func getKubectlConfigFlags(flags *genericclioptions.ConfigFlags) []string {
appendStringFlag(o, flags.Password, "password") appendStringFlag(o, flags.Password, "password")
appendStringFlag(o, flags.ClusterName, "cluster") appendStringFlag(o, flags.ClusterName, "cluster")
appendStringFlag(o, flags.AuthInfoName, "user") appendStringFlag(o, flags.AuthInfoName, "user")
//appendStringFlag(o, flags.Namespace, "namespace")
appendStringFlag(o, flags.Context, "context") appendStringFlag(o, flags.Context, "context")
appendStringFlag(o, flags.APIServer, "server") appendStringFlag(o, flags.APIServer, "server")
appendBoolFlag(o, flags.Insecure, "insecure-skip-tls-verify") appendBoolFlag(o, flags.Insecure, "insecure-skip-tls-verify")
@ -128,7 +128,7 @@ func appendBoolFlag(out *[]string, in *bool, flag string) {
} }
} }
func appendStringArrayFlag(out *[]string, in *[]string, flag string) { func appendStringArrayFlag(out, in *[]string, flag string) {
if in != nil && len(*in) > 0 { if in != nil && len(*in) > 0 {
*out = append(*out, fmt.Sprintf("--%v=%v'", flag, strings.Join(*in, ","))) *out = append(*out, fmt.Sprintf("--%v=%v'", flag, strings.Join(*in, ",")))
} }

View file

@ -35,7 +35,10 @@ type DeploymentLint struct {
// Check returns true if the lint detects an issue // Check returns true if the lint detects an issue
func (lint DeploymentLint) Check(obj kmeta.Object) bool { func (lint DeploymentLint) Check(obj kmeta.Object) bool {
cmp := obj.(*v1.Deployment) cmp, ok := obj.(*v1.Deployment)
if !ok {
util.PrintError(fmt.Errorf("unexpected type: %T", obj))
}
return lint.f(*cmp) return lint.f(*cmp)
} }
@ -72,11 +75,11 @@ func removedFlag(flag string, issueNumber int, version string) DeploymentLint {
issue: issueNumber, issue: issueNumber,
version: version, version: version,
f: func(dep v1.Deployment) bool { f: func(dep v1.Deployment) bool {
if !isIngressNginxDeployment(dep) { if !isIngressNginxDeployment(&dep) {
return false return false
} }
args := getNginxArgs(dep) args := getNginxArgs(&dep)
for _, arg := range args { for _, arg := range args {
if strings.HasPrefix(arg, fmt.Sprintf("--%v", flag)) { if strings.HasPrefix(arg, fmt.Sprintf("--%v", flag)) {
return true return true
@ -88,8 +91,9 @@ func removedFlag(flag string, issueNumber int, version string) DeploymentLint {
} }
} }
func getNginxArgs(dep v1.Deployment) []string { func getNginxArgs(dep *v1.Deployment) []string {
for _, container := range dep.Spec.Template.Spec.Containers { for i := range dep.Spec.Template.Spec.Containers {
container := &dep.Spec.Template.Spec.Containers[i]
if len(container.Args) > 0 && container.Args[0] == "/nginx-ingress-controller" { if len(container.Args) > 0 && container.Args[0] == "/nginx-ingress-controller" {
return container.Args return container.Args
} }
@ -97,10 +101,10 @@ func getNginxArgs(dep v1.Deployment) []string {
return make([]string, 0) return make([]string, 0)
} }
func isIngressNginxDeployment(dep v1.Deployment) bool { func isIngressNginxDeployment(dep *v1.Deployment) bool {
containers := dep.Spec.Template.Spec.Containers containers := dep.Spec.Template.Spec.Containers
for _, container := range containers { for i := range containers {
if len(container.Args) > 0 && container.Args[0] == "/nginx-ingress-controller" { if len(containers[i].Args) > 0 && containers[i].Args[0] == "/nginx-ingress-controller" {
return true return true
} }
} }

View file

@ -30,13 +30,16 @@ type IngressLint struct {
message string message string
issue int issue int
version string version string
f func(ing networking.Ingress) bool f func(ing *networking.Ingress) bool
} }
// Check returns true if the lint detects an issue // Check returns true if the lint detects an issue
func (lint IngressLint) Check(obj kmeta.Object) bool { func (lint IngressLint) Check(obj kmeta.Object) bool {
ing := obj.(*networking.Ingress) ing, ok := obj.(*networking.Ingress)
return lint.f(*ing) if !ok {
util.PrintError(fmt.Errorf("unexpected type: %T", obj))
}
return lint.f(ing)
} }
// Message is a description of the lint // Message is a description of the lint
@ -94,7 +97,7 @@ func GetIngressLints() []IngressLint {
} }
} }
func xForwardedPrefixIsBool(ing networking.Ingress) bool { func xForwardedPrefixIsBool(ing *networking.Ingress) bool {
for name, val := range ing.Annotations { for name, val := range ing.Annotations {
if strings.HasSuffix(name, "/x-forwarded-prefix") && (val == "true" || val == "false") { if strings.HasSuffix(name, "/x-forwarded-prefix") && (val == "true" || val == "false") {
return true return true
@ -103,7 +106,7 @@ func xForwardedPrefixIsBool(ing networking.Ingress) bool {
return false return false
} }
func annotationPrefixIsNginxCom(ing networking.Ingress) bool { func annotationPrefixIsNginxCom(ing *networking.Ingress) bool {
for name := range ing.Annotations { for name := range ing.Annotations {
if strings.HasPrefix(name, "nginx.com/") { if strings.HasPrefix(name, "nginx.com/") {
return true return true
@ -112,7 +115,7 @@ func annotationPrefixIsNginxCom(ing networking.Ingress) bool {
return false return false
} }
func annotationPrefixIsNginxOrg(ing networking.Ingress) bool { func annotationPrefixIsNginxOrg(ing *networking.Ingress) bool {
for name := range ing.Annotations { for name := range ing.Annotations {
if strings.HasPrefix(name, "nginx.org/") { if strings.HasPrefix(name, "nginx.org/") {
return true return true
@ -121,7 +124,7 @@ func annotationPrefixIsNginxOrg(ing networking.Ingress) bool {
return false return false
} }
func rewriteTargetWithoutCaptureGroup(ing networking.Ingress) bool { func rewriteTargetWithoutCaptureGroup(ing *networking.Ingress) bool {
for name, val := range ing.Annotations { for name, val := range ing.Annotations {
if strings.HasSuffix(name, "/rewrite-target") && !strings.Contains(val, "$1") { if strings.HasSuffix(name, "/rewrite-target") && !strings.Contains(val, "$1") {
return true return true
@ -135,7 +138,7 @@ func removedAnnotation(annotationName string, issueNumber int, version string) I
message: fmt.Sprintf("Contains the removed %v annotation.", annotationName), message: fmt.Sprintf("Contains the removed %v annotation.", annotationName),
issue: issueNumber, issue: issueNumber,
version: version, version: version,
f: func(ing networking.Ingress) bool { f: func(ing *networking.Ingress) bool {
for annotation := range ing.Annotations { for annotation := range ing.Annotations {
if strings.HasSuffix(annotation, "/"+annotationName) { if strings.HasSuffix(annotation, "/"+annotationName) {
return true return true
@ -146,7 +149,7 @@ func removedAnnotation(annotationName string, issueNumber int, version string) I
} }
} }
func satisfyDirective(ing networking.Ingress) bool { func satisfyDirective(ing *networking.Ingress) bool {
for name, val := range ing.Annotations { for name, val := range ing.Annotations {
if strings.HasSuffix(name, "/configuration-snippet") { if strings.HasSuffix(name, "/configuration-snippet") {
return strings.Contains(val, "satisfy") return strings.Contains(val, "satisfy")

View file

@ -24,7 +24,7 @@ import (
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
//Just importing this is supposed to allow cloud authentication // Just importing this is supposed to allow cloud authentication
// eg GCP, AWS, Azure ... // eg GCP, AWS, Azure ...
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"

View file

@ -35,7 +35,7 @@ import (
) )
// ChoosePod finds a pod either by deployment or by name // ChoosePod finds a pod either by deployment or by name
func ChoosePod(flags *genericclioptions.ConfigFlags, podName string, deployment string, selector string) (apiv1.Pod, error) { func ChoosePod(flags *genericclioptions.ConfigFlags, podName, deployment, selector string) (apiv1.Pod, error) {
if podName != "" { if podName != "" {
return GetNamedPod(flags, podName) return GetNamedPod(flags, podName)
} }
@ -54,9 +54,9 @@ func GetNamedPod(flags *genericclioptions.ConfigFlags, name string) (apiv1.Pod,
return apiv1.Pod{}, err return apiv1.Pod{}, err
} }
for _, pod := range allPods { for i := range allPods {
if pod.Name == name { if allPods[i].Name == name {
return pod, nil return allPods[i], nil
} }
} }
@ -132,7 +132,7 @@ func GetIngressDefinitions(flags *genericclioptions.ConfigFlags, namespace strin
} }
// GetNumEndpoints counts the number of endpointslices adresses for the service with the given name // GetNumEndpoints counts the number of endpointslices adresses for the service with the given name
func GetNumEndpoints(flags *genericclioptions.ConfigFlags, namespace string, serviceName string) (*int, error) { func GetNumEndpoints(flags *genericclioptions.ConfigFlags, namespace, serviceName string) (*int, error) {
epss, err := GetEndpointSlicesByName(flags, namespace, serviceName) epss, err := GetEndpointSlicesByName(flags, namespace, serviceName)
if err != nil { if err != nil {
return nil, err return nil, err
@ -143,25 +143,26 @@ func GetNumEndpoints(flags *genericclioptions.ConfigFlags, namespace string, ser
} }
ret := 0 ret := 0
for _, eps := range epss { for i := range epss {
for _, ep := range eps.Endpoints { eps := &epss[i]
ret += len(ep.Addresses) for j := range eps.Endpoints {
ret += len(eps.Endpoints[j].Addresses)
} }
} }
return &ret, nil return &ret, nil
} }
// GetEndpointSlicesByName returns the endpointSlices for the service with the given name // GetEndpointSlicesByName returns the endpointSlices for the service with the given name
func GetEndpointSlicesByName(flags *genericclioptions.ConfigFlags, namespace string, name string) ([]discoveryv1.EndpointSlice, error) { func GetEndpointSlicesByName(flags *genericclioptions.ConfigFlags, namespace, name string) ([]discoveryv1.EndpointSlice, error) {
allEndpointsSlices, err := getEndpointSlices(flags, namespace) allEndpointsSlices, err := getEndpointSlices(flags, namespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var eps []discoveryv1.EndpointSlice var eps []discoveryv1.EndpointSlice
for _, slice := range allEndpointsSlices { for i := range allEndpointsSlices {
if svcName, ok := slice.ObjectMeta.GetLabels()[discoveryv1.LabelServiceName]; ok { if svcName, ok := allEndpointsSlices[i].ObjectMeta.GetLabels()[discoveryv1.LabelServiceName]; ok {
if svcName == name { if svcName == name {
eps = append(eps, slice) eps = append(eps, allEndpointsSlices[i])
} }
} }
} }
@ -182,7 +183,7 @@ func getEndpointSlices(flags *genericclioptions.ConfigFlags, namespace string) (
tryAllNamespacesEndpointSlicesCache(flags) tryAllNamespacesEndpointSlicesCache(flags)
} }
cachedEndpointSlices = tryFilteringEndpointSlicesFromAllNamespacesCache(flags, namespace) cachedEndpointSlices = tryFilteringEndpointSlicesFromAllNamespacesCache(namespace)
if cachedEndpointSlices != nil { if cachedEndpointSlices != nil {
return *cachedEndpointSlices, nil return *cachedEndpointSlices, nil
@ -217,13 +218,13 @@ func tryAllNamespacesEndpointSlicesCache(flags *genericclioptions.ConfigFlags) {
} }
} }
func tryFilteringEndpointSlicesFromAllNamespacesCache(flags *genericclioptions.ConfigFlags, namespace string) *[]discoveryv1.EndpointSlice { func tryFilteringEndpointSlicesFromAllNamespacesCache(namespace string) *[]discoveryv1.EndpointSlice {
allEndpointSlices := endpointSlicesCache[""] allEndpointSlices := endpointSlicesCache[""]
if allEndpointSlices != nil { if allEndpointSlices != nil {
endpointSlices := make([]discoveryv1.EndpointSlice, 0) endpointSlices := make([]discoveryv1.EndpointSlice, 0)
for _, slice := range *allEndpointSlices { for i := range *allEndpointSlices {
if slice.Namespace == namespace { if (*allEndpointSlices)[i].Namespace == namespace {
endpointSlices = append(endpointSlices, slice) endpointSlices = append(endpointSlices, (*allEndpointSlices)[i])
} }
} }
endpointSlicesCache[namespace] = &endpointSlices endpointSlicesCache[namespace] = &endpointSlices
@ -242,9 +243,9 @@ func GetServiceByName(flags *genericclioptions.ConfigFlags, name string, service
services = &servicesArray services = &servicesArray
} }
for _, svc := range *services { for i := range *services {
if svc.Name == name { if (*services)[i].Name == name {
return svc, nil return (*services)[i], nil
} }
} }
@ -288,7 +289,6 @@ func getLabeledPods(flags *genericclioptions.ConfigFlags, label string) ([]apiv1
pods, err := api.Pods(namespace).List(context.TODO(), metav1.ListOptions{ pods, err := api.Pods(namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: label, LabelSelector: label,
}) })
if err != nil { if err != nil {
return make([]apiv1.Pod, 0), err return make([]apiv1.Pod, 0), err
} }
@ -303,9 +303,9 @@ func getDeploymentPods(flags *genericclioptions.ConfigFlags, deployment string)
} }
ingressPods := make([]apiv1.Pod, 0) ingressPods := make([]apiv1.Pod, 0)
for _, pod := range pods { for i := range pods {
if util.PodInDeployment(pod, deployment) { if util.PodInDeployment(&pods[i], deployment) {
ingressPods = append(ingressPods, pod) ingressPods = append(ingressPods, pods[i])
} }
} }
@ -331,5 +331,4 @@ func getServices(flags *genericclioptions.ConfigFlags) ([]apiv1.Service, error)
} }
return services.Items, nil return services.Items, nil
} }

View file

@ -47,17 +47,25 @@ func PrintError(e error) {
} }
// ParseVersionString returns the major, minor, and patch numbers of a version string // ParseVersionString returns the major, minor, and patch numbers of a version string
func ParseVersionString(v string) (int, int, int, error) { func ParseVersionString(v string) (major, minor, patch int, err error) {
parts := versionRegex.FindStringSubmatch(v) parts := versionRegex.FindStringSubmatch(v)
if len(parts) != 4 { if len(parts) != 4 {
return 0, 0, 0, fmt.Errorf("could not parse %v as a version string (like 0.20.3)", v) return 0, 0, 0, fmt.Errorf("could not parse %v as a version string (like 0.20.3)", v)
} }
major, _ := strconv.Atoi(parts[1]) major, err = strconv.Atoi(parts[1])
minor, _ := strconv.Atoi(parts[2]) if err != nil {
patch, _ := strconv.Atoi(parts[3]) return 0, 0, 0, err
}
minor, err = strconv.Atoi(parts[2])
if err != nil {
return 0, 0, 0, err
}
patch, err = strconv.Atoi(parts[3])
if err != nil {
return 0, 0, 0, err
}
return major, minor, patch, nil return major, minor, patch, nil
} }
@ -90,7 +98,7 @@ func isVersionLessThan(a, b string) bool {
// PodInDeployment returns whether a pod is part of a deployment with the given name // PodInDeployment returns whether a pod is part of a deployment with the given name
// a pod is considered to be in {deployment} if it is owned by a replicaset with a name of format {deployment}-otherchars // a pod is considered to be in {deployment} if it is owned by a replicaset with a name of format {deployment}-otherchars
func PodInDeployment(pod apiv1.Pod, deployment string) bool { func PodInDeployment(pod *apiv1.Pod, deployment string) bool {
for _, owner := range pod.OwnerReferences { for _, owner := range pod.OwnerReferences {
if owner.Controller == nil || !*owner.Controller || owner.Kind != "ReplicaSet" { if owner.Controller == nil || !*owner.Controller || owner.Kind != "ReplicaSet" {
continue continue
@ -138,7 +146,7 @@ func AddContainerFlag(cmd *cobra.Command) *string {
// GetNamespace takes a set of kubectl flag values and returns the namespace we should be operating in // GetNamespace takes a set of kubectl flag values and returns the namespace we should be operating in
func GetNamespace(flags *genericclioptions.ConfigFlags) string { func GetNamespace(flags *genericclioptions.ConfigFlags) string {
namespace, _, err := flags.ToRawKubeConfigLoader().Namespace() namespace, _, err := flags.ToRawKubeConfigLoader().Namespace()
if err != nil || len(namespace) == 0 { if err != nil || namespace == "" {
namespace = apiv1.NamespaceDefault namespace = apiv1.NamespaceDefault
} }
return namespace return namespace

View file

@ -716,7 +716,7 @@ Do not try to edit it manually.
### [[Flag] watch namespace selector](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/settings/namespace_selector.go#L30) ### [[Flag] watch namespace selector](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/settings/namespace_selector.go#L30)
- [should ingore Ingress of namespace without label foo=bar and accept those of namespace with label foo=bar](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/settings/namespace_selector.go#L63) - [should ignore Ingress of namespace without label foo=bar and accept those of namespace with label foo=bar](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/settings/namespace_selector.go#L63)
### [[Security] no-auth-locations](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/settings/no_auth_locations.go#L33) ### [[Security] no-auth-locations](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/settings/no_auth_locations.go#L33)

View file

@ -16,13 +16,13 @@ func hello(w http.ResponseWriter, r *http.Request) {
} }
key := keys[0] key := keys[0]
fmt.Fprintf(w, "Hello "+string(key)+"!") fmt.Fprintf(w, "Hello "+key+"!")
} }
func main() { func main() {
http.HandleFunc("/hello", hello) http.HandleFunc("/hello", hello)
l, err := net.Listen("tcp", "0.0.0.0:9000") l, err := net.Listen("tcp", "0.0.0.0:9000") //nolint:gosec // Ignore the gosec error since it's a hello server
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -41,7 +41,7 @@ type hwServer struct {
} }
// SayHello implements helloworld.GreeterServer // SayHello implements helloworld.GreeterServer
func (s *hwServer) SayHello(ctx context.Context, in *hwpb.HelloRequest) (*hwpb.HelloReply, error) { func (s *hwServer) SayHello(_ context.Context, in *hwpb.HelloRequest) (*hwpb.HelloReply, error) {
return &hwpb.HelloReply{Message: "Hello " + in.Name}, nil return &hwpb.HelloReply{Message: "Hello " + in.Name}, nil
} }
@ -49,7 +49,7 @@ type ecServer struct {
ecpb.UnimplementedEchoServer ecpb.UnimplementedEchoServer
} }
func (s *ecServer) UnaryEcho(ctx context.Context, req *ecpb.EchoRequest) (*ecpb.EchoResponse, error) { func (s *ecServer) UnaryEcho(_ context.Context, req *ecpb.EchoRequest) (*ecpb.EchoResponse, error) {
return &ecpb.EchoResponse{Message: req.Message}, nil return &ecpb.EchoResponse{Message: req.Message}, nil
} }

View file

@ -42,19 +42,16 @@ type IngressAdmission struct {
Checker Checker Checker Checker
} }
var ( var ingressResource = metav1.GroupVersionKind{
ingressResource = metav1.GroupVersionKind{ Group: networking.GroupName,
Group: networking.GroupName, Version: "v1",
Version: "v1", Kind: "Ingress",
Kind: "Ingress", }
}
)
// HandleAdmission populates the admission Response // HandleAdmission populates the admission Response
// with Allowed=false if the Object is an ingress that would prevent nginx to reload the configuration // with Allowed=false if the Object is an ingress that would prevent nginx to reload the configuration
// with Allowed=true otherwise // with Allowed=true otherwise
func (ia *IngressAdmission) HandleAdmission(obj runtime.Object) (runtime.Object, error) { func (ia *IngressAdmission) HandleAdmission(obj runtime.Object) (runtime.Object, error) {
review, isV1 := obj.(*admissionv1.AdmissionReview) review, isV1 := obj.(*admissionv1.AdmissionReview)
if !isV1 { if !isV1 {
return nil, fmt.Errorf("request is not of type AdmissionReview v1 or v1beta1") return nil, fmt.Errorf("request is not of type AdmissionReview v1 or v1beta1")

View file

@ -33,12 +33,12 @@ type failTestChecker struct {
t *testing.T t *testing.T
} }
func (ftc failTestChecker) CheckIngress(ing *networking.Ingress) error { func (ftc failTestChecker) CheckIngress(_ *networking.Ingress) error {
ftc.t.Error("checker should not be called") ftc.t.Error("checker should not be called")
return nil return nil
} }
func (ftc failTestChecker) CheckWarning(ing *networking.Ingress) ([]string, error) { func (ftc failTestChecker) CheckWarning(_ *networking.Ingress) ([]string, error) {
ftc.t.Error("checker should not be called") ftc.t.Error("checker should not be called")
return nil, nil return nil, nil
} }

View file

@ -26,9 +26,7 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
) )
var ( var scheme = runtime.NewScheme()
scheme = runtime.NewScheme()
)
func init() { func init() {
if err := admissionv1.AddToScheme(scheme); err != nil { if err := admissionv1.AddToScheme(scheme); err != nil {

View file

@ -72,7 +72,7 @@ func (a alias) Parse(ing *networking.Ingress) (interface{}, error) {
aliases := sets.NewString() aliases := sets.NewString()
for _, alias := range strings.Split(val, ",") { for _, alias := range strings.Split(val, ",") {
alias = strings.TrimSpace(alias) alias = strings.TrimSpace(alias)
if len(alias) == 0 { if alias == "" {
continue continue
} }

View file

@ -65,9 +65,9 @@ func TestParse(t *testing.T) {
if testCase.skipValidation { if testCase.skipValidation {
parser.EnableAnnotationValidation = false parser.EnableAnnotationValidation = false
} }
defer func() { t.Cleanup(func() {
parser.EnableAnnotationValidation = true parser.EnableAnnotationValidation = true
}() })
result, err := ap.Parse(ing) result, err := ap.Parse(ing)
if (err != nil) != testCase.wantErr { if (err != nil) != testCase.wantErr {
t.Errorf("ParseAliasAnnotation() annotation: %s, error = %v, wantErr %v", testCase.annotations, err, testCase.wantErr) t.Errorf("ParseAliasAnnotation() annotation: %s, error = %v, wantErr %v", testCase.annotations, err, testCase.wantErr)

View file

@ -86,37 +86,36 @@ type Ingress struct {
CorsConfig cors.Config CorsConfig cors.Config
CustomHTTPErrors []int CustomHTTPErrors []int
DefaultBackend *apiv1.Service DefaultBackend *apiv1.Service
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved FastCGI fastcgi.Config
FastCGI fastcgi.Config Denied *string
Denied *string ExternalAuth authreq.Config
ExternalAuth authreq.Config EnableGlobalAuth bool
EnableGlobalAuth bool HTTP2PushPreload bool
HTTP2PushPreload bool Opentracing opentracing.Config
Opentracing opentracing.Config Opentelemetry opentelemetry.Config
Opentelemetry opentelemetry.Config Proxy proxy.Config
Proxy proxy.Config ProxySSL proxyssl.Config
ProxySSL proxyssl.Config RateLimit ratelimit.Config
RateLimit ratelimit.Config GlobalRateLimit globalratelimit.Config
GlobalRateLimit globalratelimit.Config Redirect redirect.Config
Redirect redirect.Config Rewrite rewrite.Config
Rewrite rewrite.Config Satisfy string
Satisfy string ServerSnippet string
ServerSnippet string ServiceUpstream bool
ServiceUpstream bool SessionAffinity sessionaffinity.Config
SessionAffinity sessionaffinity.Config SSLPassthrough bool
SSLPassthrough bool UsePortInRedirects bool
UsePortInRedirects bool UpstreamHashBy upstreamhashby.Config
UpstreamHashBy upstreamhashby.Config LoadBalancing string
LoadBalancing string UpstreamVhost string
UpstreamVhost string Denylist ipdenylist.SourceRange
Denylist ipdenylist.SourceRange XForwardedPrefix string
XForwardedPrefix string SSLCipher sslcipher.Config
SSLCipher sslcipher.Config Logs log.Config
Logs log.Config ModSecurity modsecurity.Config
ModSecurity modsecurity.Config Mirror mirror.Config
Mirror mirror.Config StreamSnippet string
StreamSnippet string Allowlist ipallowlist.SourceRange
Allowlist ipallowlist.SourceRange
} }
// Extractor defines the annotation parsers to be used in the extraction of annotations // Extractor defines the annotation parsers to be used in the extraction of annotations

View file

@ -64,7 +64,11 @@ func (m mockCfg) GetService(name string) (*apiv1.Service, error) {
} }
func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) { func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
if secret, _ := m.GetSecret(name); secret != nil { secret, err := m.GetSecret(name)
if err != nil {
return nil, err
}
if secret != nil {
return &resolver.AuthSSLCert{ return &resolver.AuthSSLCert{
Secret: name, Secret: name,
CAFileName: "/opt/ca.pem", CAFileName: "/opt/ca.pem",
@ -270,9 +274,9 @@ func TestCors(t *testing.T) {
if r.CorsAllowCredentials != foo.credentials { if r.CorsAllowCredentials != foo.credentials {
t.Errorf("Returned %v but expected %v for Cors Credentials", r.CorsAllowCredentials, foo.credentials) t.Errorf("Returned %v but expected %v for Cors Credentials", r.CorsAllowCredentials, foo.credentials)
} }
} }
} }
func TestCustomHTTPErrors(t *testing.T) { func TestCustomHTTPErrors(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{}) ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress() ing := buildIngress()

View file

@ -50,7 +50,7 @@ var (
) )
var AuthSecretConfig = parser.AnnotationConfig{ var AuthSecretConfig = parser.AnnotationConfig{
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
Documentation: `This annotation defines the name of the Secret that contains the usernames and passwords which are granted access to the paths defined in the Ingress rules. `, Documentation: `This annotation defines the name of the Secret that contains the usernames and passwords which are granted access to the paths defined in the Ingress rules. `,
@ -61,20 +61,20 @@ var authSecretAnnotations = parser.Annotation{
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
AuthSecretAnnotation: AuthSecretConfig, AuthSecretAnnotation: AuthSecretConfig,
authSecretTypeAnnotation: { authSecretTypeAnnotation: {
Validator: parser.ValidateRegex(*authSecretTypeRegex, true), Validator: parser.ValidateRegex(authSecretTypeRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation what is the format of auth-secret value. Can be "auth-file" that defines the content of an htpasswd file, or "auth-map" where each key Documentation: `This annotation what is the format of auth-secret value. Can be "auth-file" that defines the content of an htpasswd file, or "auth-map" where each key
is a user and each value is the password.`, is a user and each value is the password.`,
}, },
authRealmAnnotation: { authRealmAnnotation: {
Validator: parser.ValidateRegex(*parser.CharsWithSpace, false), Validator: parser.ValidateRegex(parser.CharsWithSpace, false),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
Documentation: `This annotation defines the realm (message) that should be shown to user when authentication is requested.`, Documentation: `This annotation defines the realm (message) that should be shown to user when authentication is requested.`,
}, },
authTypeAnnotation: { authTypeAnnotation: {
Validator: parser.ValidateRegex(*authTypeRegex, true), Validator: parser.ValidateRegex(authTypeRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation defines the basic authentication type. Should be "basic" or "digest"`, Documentation: `This annotation defines the basic authentication type. Should be "basic" or "digest"`,
@ -167,14 +167,14 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
s, err := parser.GetStringAnnotation(AuthSecretAnnotation, ing, a.annotationConfig.Annotations) s, err := parser.GetStringAnnotation(AuthSecretAnnotation, ing, a.annotationConfig.Annotations)
if err != nil { if err != nil {
return nil, ing_errors.LocationDenied{ return nil, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("error reading secret name from annotation: %w", err), Reason: fmt.Errorf("error reading secret name from annotation: %w", err),
} }
} }
sns, sname, err := cache.SplitMetaNamespaceKey(s) sns, sname, err := cache.SplitMetaNamespaceKey(s)
if err != nil { if err != nil {
return nil, ing_errors.LocationDenied{ return nil, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("error reading secret name from annotation: %w", err), Reason: fmt.Errorf("error reading secret name from annotation: %w", err),
} }
} }
@ -185,7 +185,7 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
secCfg := a.r.GetSecurityConfiguration() secCfg := a.r.GetSecurityConfiguration()
// We don't accept different namespaces for secrets. // We don't accept different namespaces for secrets.
if !secCfg.AllowCrossNamespaceResources && sns != ing.Namespace { if !secCfg.AllowCrossNamespaceResources && sns != ing.Namespace {
return nil, ing_errors.LocationDenied{ return nil, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"), Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"),
} }
} }
@ -193,7 +193,7 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
name := fmt.Sprintf("%v/%v", sns, sname) name := fmt.Sprintf("%v/%v", sns, sname)
secret, err := a.r.GetSecret(name) secret, err := a.r.GetSecret(name)
if err != nil { if err != nil {
return nil, ing_errors.LocationDenied{ return nil, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("unexpected error reading secret %s: %w", name, err), Reason: fmt.Errorf("unexpected error reading secret %s: %w", name, err),
} }
} }
@ -217,7 +217,7 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
return nil, err return nil, err
} }
default: default:
return nil, ing_errors.LocationDenied{ return nil, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("invalid auth-secret-type in annotation, must be 'auth-file' or 'auth-map': %w", err), Reason: fmt.Errorf("invalid auth-secret-type in annotation, must be 'auth-file' or 'auth-map': %w", err),
} }
} }
@ -238,14 +238,14 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
func dumpSecretAuthFile(filename string, secret *api.Secret) error { func dumpSecretAuthFile(filename string, secret *api.Secret) error {
val, ok := secret.Data["auth"] val, ok := secret.Data["auth"]
if !ok { if !ok {
return ing_errors.LocationDenied{ return ing_errors.LocationDeniedError{
Reason: fmt.Errorf("the secret %s does not contain a key with value auth", secret.Name), Reason: fmt.Errorf("the secret %s does not contain a key with value auth", secret.Name),
} }
} }
err := os.WriteFile(filename, val, file.ReadWriteByUser) err := os.WriteFile(filename, val, file.ReadWriteByUser)
if err != nil { if err != nil {
return ing_errors.LocationDenied{ return ing_errors.LocationDeniedError{
Reason: fmt.Errorf("unexpected error creating password file: %w", err), Reason: fmt.Errorf("unexpected error creating password file: %w", err),
} }
} }
@ -264,7 +264,7 @@ func dumpSecretAuthMap(filename string, secret *api.Secret) error {
err := os.WriteFile(filename, []byte(builder.String()), file.ReadWriteByUser) err := os.WriteFile(filename, []byte(builder.String()), file.ReadWriteByUser)
if err != nil { if err != nil {
return ing_errors.LocationDenied{ return ing_errors.LocationDeniedError{
Reason: fmt.Errorf("unexpected error creating password file: %w", err), Reason: fmt.Errorf("unexpected error creating password file: %w", err),
} }
} }

View file

@ -32,6 +32,15 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
//nolint:gosec // Ignore hardcoded credentials error in testing
const (
authType = "basic"
authRealm = "-realm-"
defaultDemoSecret = "default/demo-secret"
othernsDemoSecret = "otherns/demo-secret"
demoSecret = "demo-secret"
)
func buildIngress() *networking.Ingress { func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{ defaultBackend := networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
@ -80,7 +89,7 @@ type mockSecret struct {
} }
func (m mockSecret) GetSecret(name string) (*api.Secret, error) { func (m mockSecret) GetSecret(name string) (*api.Secret, error) {
if name != "default/demo-secret" && name != "otherns/demo-secret" { if name != defaultDemoSecret && name != othernsDemoSecret {
return nil, fmt.Errorf("there is no secret with name %v", name) return nil, fmt.Errorf("there is no secret with name %v", name)
} }
@ -92,7 +101,7 @@ func (m mockSecret) GetSecret(name string) (*api.Secret, error) {
return &api.Secret{ return &api.Secret{
ObjectMeta: meta_v1.ObjectMeta{ ObjectMeta: meta_v1.ObjectMeta{
Namespace: ns, Namespace: ns,
Name: "demo-secret", Name: demoSecret,
}, },
Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")}, Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")},
}, nil }, nil
@ -129,9 +138,9 @@ func TestIngressInvalidRealm(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic" data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "something weird ; location trying to { break }" data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "something weird ; location trying to { break }"
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret" data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = demoSecret
ing.SetAnnotations(data) ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
@ -148,14 +157,14 @@ func TestIngressInvalidDifferentNamespace(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic" data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "otherns/demo-secret" data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = othernsDemoSecret
ing.SetAnnotations(data) ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
expected := ing_errors.LocationDenied{ expected := ing_errors.LocationDeniedError{
Reason: errors.New("cross namespace usage of secrets is not allowed"), Reason: errors.New("cross namespace usage of secrets is not allowed"),
} }
_, err := NewParser(dir, &mockSecret{}).Parse(ing) _, err := NewParser(dir, &mockSecret{}).Parse(ing)
@ -168,8 +177,8 @@ func TestIngressInvalidDifferentNamespaceAllowed(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic" data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "otherns/demo-secret" data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = othernsDemoSecret
ing.SetAnnotations(data) ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
@ -187,14 +196,14 @@ func TestIngressInvalidSecretName(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic" data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret;xpto" data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret;xpto"
ing.SetAnnotations(data) ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
expected := ing_errors.LocationDenied{ expected := ing_errors.LocationDeniedError{
Reason: errors.New("error reading secret name from annotation: annotation nginx.ingress.kubernetes.io/auth-secret contains invalid value"), Reason: errors.New("error reading secret name from annotation: annotation nginx.ingress.kubernetes.io/auth-secret contains invalid value"),
} }
_, err := NewParser(dir, &mockSecret{}).Parse(ing) _, err := NewParser(dir, &mockSecret{}).Parse(ing)
@ -207,13 +216,13 @@ func TestInvalidIngressAuthNoSecret(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic" data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
ing.SetAnnotations(data) ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
expected := ing_errors.LocationDenied{ expected := ing_errors.LocationDeniedError{
Reason: errors.New("error reading secret name from annotation: ingress rule without annotations"), Reason: errors.New("error reading secret name from annotation: ingress rule without annotations"),
} }
_, err := NewParser(dir, &mockSecret{}).Parse(ing) _, err := NewParser(dir, &mockSecret{}).Parse(ing)
@ -226,9 +235,9 @@ func TestIngressAuth(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic" data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret" data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = demoSecret
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "-realm-" data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = authRealm
ing.SetAnnotations(data) ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
@ -242,10 +251,10 @@ func TestIngressAuth(t *testing.T) {
if !ok { if !ok {
t.Errorf("expected a BasicDigest type") t.Errorf("expected a BasicDigest type")
} }
if auth.Type != "basic" { if auth.Type != authType {
t.Errorf("Expected basic as auth type but returned %s", auth.Type) t.Errorf("Expected basic as auth type but returned %s", auth.Type)
} }
if auth.Realm != "-realm-" { if auth.Realm != authRealm {
t.Errorf("Expected -realm- as realm but returned %s", auth.Realm) t.Errorf("Expected -realm- as realm but returned %s", auth.Realm)
} }
if !auth.Secured { if !auth.Secured {
@ -257,9 +266,9 @@ func TestIngressAuthWithoutSecret(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic" data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "invalid-secret" data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "invalid-secret"
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "-realm-" data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = authRealm
ing.SetAnnotations(data) ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
@ -275,10 +284,10 @@ func TestIngressAuthInvalidSecretKey(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic" data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret" data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = demoSecret
data[parser.GetAnnotationWithPrefix(authSecretTypeAnnotation)] = "invalid-type" data[parser.GetAnnotationWithPrefix(authSecretTypeAnnotation)] = "invalid-type"
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "-realm-" data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = authRealm
ing.SetAnnotations(data) ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t) _, dir, _ := dummySecretContent(t)
@ -290,7 +299,7 @@ func TestIngressAuthInvalidSecretKey(t *testing.T) {
} }
} }
func dummySecretContent(t *testing.T) (string, string, *api.Secret) { func dummySecretContent(t *testing.T) (fileName, dir string, s *api.Secret) {
dir, err := os.MkdirTemp("", fmt.Sprintf("%v", time.Now().Unix())) dir, err := os.MkdirTemp("", fmt.Sprintf("%v", time.Now().Unix()))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -301,7 +310,10 @@ func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
t.Error(err) t.Error(err)
} }
defer tmpfile.Close() defer tmpfile.Close()
s, _ := mockSecret{}.GetSecret("default/demo-secret") s, err = mockSecret{}.GetSecret(defaultDemoSecret)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
return tmpfile.Name(), dir, s return tmpfile.Name(), dir, s
} }

View file

@ -57,25 +57,25 @@ var authReqAnnotations = parser.Annotation{
Group: "authentication", Group: "authentication",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
authReqURLAnnotation: { authReqURLAnnotation: {
Validator: parser.ValidateRegex(*parser.URLWithNginxVariableRegex, true), Validator: parser.ValidateRegex(parser.URLWithNginxVariableRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskHigh, Risk: parser.AnnotationRiskHigh,
Documentation: `This annotation allows to indicate the URL where the HTTP request should be sent`, Documentation: `This annotation allows to indicate the URL where the HTTP request should be sent`,
}, },
authReqMethodAnnotation: { authReqMethodAnnotation: {
Validator: parser.ValidateRegex(*methodsRegex, true), Validator: parser.ValidateRegex(methodsRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation allows to specify the HTTP method to use`, Documentation: `This annotation allows to specify the HTTP method to use`,
}, },
authReqSigninAnnotation: { authReqSigninAnnotation: {
Validator: parser.ValidateRegex(*parser.URLWithNginxVariableRegex, true), Validator: parser.ValidateRegex(parser.URLWithNginxVariableRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskHigh, Risk: parser.AnnotationRiskHigh,
Documentation: `This annotation allows to specify the location of the error page`, Documentation: `This annotation allows to specify the location of the error page`,
}, },
authReqSigninRedirParamAnnotation: { authReqSigninRedirParamAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true), Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation allows to specify the URL parameter in the error page which should contain the original URL for a failed signin request`, Documentation: `This annotation allows to specify the URL parameter in the error page which should contain the original URL for a failed signin request`,
@ -87,7 +87,7 @@ var authReqAnnotations = parser.Annotation{
Documentation: `This annotation allows to specify a custom snippet to use with external authentication`, Documentation: `This annotation allows to specify a custom snippet to use with external authentication`,
}, },
authReqCacheKeyAnnotation: { authReqCacheKeyAnnotation: {
Validator: parser.ValidateRegex(*parser.NGINXVariable, true), Validator: parser.ValidateRegex(parser.NGINXVariable, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation enables caching for auth requests.`, Documentation: `This annotation enables caching for auth requests.`,
@ -117,26 +117,26 @@ var authReqAnnotations = parser.Annotation{
Documentation: `This annotation specifies a duration in seconds which an idle keepalive connection to an upstream server will stay open`, Documentation: `This annotation specifies a duration in seconds which an idle keepalive connection to an upstream server will stay open`,
}, },
authReqCacheDuration: { authReqCacheDuration: {
Validator: parser.ValidateRegex(*parser.ExtendedCharsRegex, false), Validator: parser.ValidateRegex(parser.ExtendedCharsRegex, false),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation allows to specify a caching time for auth responses based on their response codes, e.g. 200 202 30m`, Documentation: `This annotation allows to specify a caching time for auth responses based on their response codes, e.g. 200 202 30m`,
}, },
authReqResponseHeadersAnnotation: { authReqResponseHeadersAnnotation: {
Validator: parser.ValidateRegex(*parser.HeadersVariable, true), Validator: parser.ValidateRegex(parser.HeadersVariable, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation sets the headers to pass to backend once authentication request completes. They should be separated by comma.`, Documentation: `This annotation sets the headers to pass to backend once authentication request completes. They should be separated by comma.`,
}, },
authReqProxySetHeadersAnnotation: { authReqProxySetHeadersAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation sets the name of a ConfigMap that specifies headers to pass to the authentication service. Documentation: `This annotation sets the name of a ConfigMap that specifies headers to pass to the authentication service.
Only ConfigMaps on the same namespace are allowed`, Only ConfigMaps on the same namespace are allowed`,
}, },
authReqRequestRedirectAnnotation: { authReqRequestRedirectAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true), Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation allows to specify the X-Auth-Request-Redirect header value`, Documentation: `This annotation allows to specify the X-Auth-Request-Redirect header value`,
@ -249,8 +249,8 @@ func (e1 *Config) Equal(e2 *Config) bool {
var ( var (
methodsRegex = regexp.MustCompile("(GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT|OPTIONS|TRACE)") methodsRegex = regexp.MustCompile("(GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT|OPTIONS|TRACE)")
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`) headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
statusCodeRegex = regexp.MustCompile(`^[\d]{3}$`) statusCodeRegex = regexp.MustCompile(`^\d{3}$`)
durationRegex = regexp.MustCompile(`^[\d]+(ms|s|m|h|d|w|M|y)$`) // see http://nginx.org/en/docs/syntax.html durationRegex = regexp.MustCompile(`^\d+(ms|s|m|h|d|w|M|y)$`) // see http://nginx.org/en/docs/syntax.html
) )
// ValidMethod checks is the provided string a valid HTTP method // ValidMethod checks is the provided string a valid HTTP method
@ -273,7 +273,7 @@ func ValidCacheDuration(duration string) bool {
seenDuration := false seenDuration := false
for _, element := range elements { for _, element := range elements {
if len(element) == 0 { if element == "" {
continue continue
} }
if statusCodeRegex.MatchString(element) { if statusCodeRegex.MatchString(element) {
@ -304,6 +304,8 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress // ParseAnnotations parses the annotations contained in the ingress
// rule used to use an Config URL as source for authentication // rule used to use an Config URL as source for authentication
//
//nolint:gocyclo // Ignore function complexity error
func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) { func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
// Required Parameters // Required Parameters
urlString, err := parser.GetStringAnnotation(authReqURLAnnotation, ing, a.annotationConfig.Annotations) urlString, err := parser.GetStringAnnotation(authReqURLAnnotation, ing, a.annotationConfig.Annotations)
@ -313,7 +315,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
authURL, err := parser.StringToURL(urlString) authURL, err := parser.StringToURL(urlString)
if err != nil { if err != nil {
return nil, ing_errors.LocationDenied{Reason: fmt.Errorf("could not parse auth-url annotation: %v", err)} return nil, ing_errors.LocationDeniedError{Reason: fmt.Errorf("could not parse auth-url annotation: %v", err)}
} }
authMethod, err := parser.GetStringAnnotation(authReqMethodAnnotation, ing, a.annotationConfig.Annotations) authMethod, err := parser.GetStringAnnotation(authReqMethodAnnotation, ing, a.annotationConfig.Annotations)
@ -410,7 +412,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
if err != nil && ing_errors.IsValidationError(err) { if err != nil && ing_errors.IsValidationError(err) {
return nil, ing_errors.NewLocationDenied("validation error") return nil, ing_errors.NewLocationDenied("validation error")
} }
if len(hstr) != 0 { if hstr != "" {
harr := strings.Split(hstr, ",") harr := strings.Split(hstr, ",")
for _, header := range harr { for _, header := range harr {
header = strings.TrimSpace(header) header = strings.TrimSpace(header)
@ -430,7 +432,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
cns, _, err := cache.SplitMetaNamespaceKey(proxySetHeaderMap) cns, _, err := cache.SplitMetaNamespaceKey(proxySetHeaderMap)
if err != nil { if err != nil {
return nil, ing_errors.LocationDenied{ return nil, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("error reading configmap name %s from annotation: %w", proxySetHeaderMap, err), Reason: fmt.Errorf("error reading configmap name %s from annotation: %w", proxySetHeaderMap, err),
} }
} }
@ -442,7 +444,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
secCfg := a.r.GetSecurityConfiguration() secCfg := a.r.GetSecurityConfiguration()
// We don't accept different namespaces for secrets. // We don't accept different namespaces for secrets.
if !secCfg.AllowCrossNamespaceResources && cns != ing.Namespace { if !secCfg.AllowCrossNamespaceResources && cns != ing.Namespace {
return nil, ing_errors.LocationDenied{ return nil, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"), Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"),
} }
} }
@ -499,7 +501,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
// It will always return at least one duration (the default duration) // It will always return at least one duration (the default duration)
func ParseStringToCacheDurations(input string) ([]string, error) { func ParseStringToCacheDurations(input string) ([]string, error) {
authCacheDuration := []string{} authCacheDuration := []string{}
if len(input) != 0 { if input != "" {
arr := strings.Split(input, ",") arr := strings.Split(input, ",")
for _, duration := range arr { for _, duration := range arr {
duration = strings.TrimSpace(duration) duration = strings.TrimSpace(duration)

View file

@ -17,7 +17,6 @@ limitations under the License.
package authreq package authreq
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
@ -113,7 +112,7 @@ func TestAnnotations(t *testing.T) {
data[parser.GetAnnotationWithPrefix("auth-url")] = test.url data[parser.GetAnnotationWithPrefix("auth-url")] = test.url
data[parser.GetAnnotationWithPrefix("auth-signin")] = test.signinURL data[parser.GetAnnotationWithPrefix("auth-signin")] = test.signinURL
data[parser.GetAnnotationWithPrefix("auth-signin-redirect-param")] = test.signinURLRedirectParam data[parser.GetAnnotationWithPrefix("auth-signin-redirect-param")] = test.signinURLRedirectParam
data[parser.GetAnnotationWithPrefix("auth-method")] = fmt.Sprintf("%v", test.method) data[parser.GetAnnotationWithPrefix("auth-method")] = test.method
data[parser.GetAnnotationWithPrefix("auth-request-redirect")] = test.requestRedirect data[parser.GetAnnotationWithPrefix("auth-request-redirect")] = test.requestRedirect
data[parser.GetAnnotationWithPrefix("auth-snippet")] = test.authSnippet data[parser.GetAnnotationWithPrefix("auth-snippet")] = test.authSnippet
data[parser.GetAnnotationWithPrefix("auth-cache-key")] = test.authCacheKey data[parser.GetAnnotationWithPrefix("auth-cache-key")] = test.authCacheKey
@ -331,7 +330,6 @@ func TestKeepaliveAnnotations(t *testing.T) {
} }
func TestParseStringToCacheDurations(t *testing.T) { func TestParseStringToCacheDurations(t *testing.T) {
tests := []struct { tests := []struct {
title string title string
duration string duration string
@ -346,7 +344,6 @@ func TestParseStringToCacheDurations(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
dur, err := ParseStringToCacheDurations(test.duration) dur, err := ParseStringToCacheDurations(test.duration)
if test.expErr { if test.expErr {
if err == nil { if err == nil {

View file

@ -55,7 +55,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress // ParseAnnotations parses the annotations contained in the ingress
// rule used to enable or disable global external authentication // rule used to enable or disable global external authentication
func (a authReqGlobal) Parse(ing *networking.Ingress) (interface{}, error) { func (a authReqGlobal) Parse(ing *networking.Ingress) (interface{}, error) {
enableGlobalAuth, err := parser.GetBoolAnnotation(enableGlobalAuthAnnotation, ing, a.annotationConfig.Annotations) enableGlobalAuth, err := parser.GetBoolAnnotation(enableGlobalAuthAnnotation, ing, a.annotationConfig.Annotations)
if err != nil { if err != nil {
enableGlobalAuth = true enableGlobalAuth = true

View file

@ -77,7 +77,10 @@ func TestAnnotation(t *testing.T) {
data[parser.GetAnnotationWithPrefix("enable-global-auth")] = "false" data[parser.GetAnnotationWithPrefix("enable-global-auth")] = "false"
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, _ := NewParser(&resolver.Mock{}).Parse(ing) i, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
u, ok := i.(bool) u, ok := i.(bool)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")

View file

@ -18,11 +18,10 @@ package authtls
import ( import (
"fmt" "fmt"
"regexp"
networking "k8s.io/api/networking/v1" networking "k8s.io/api/networking/v1"
"regexp"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors" ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
@ -45,20 +44,20 @@ var (
regexChars = regexp.QuoteMeta(`()|=`) regexChars = regexp.QuoteMeta(`()|=`)
authVerifyClientRegex = regexp.MustCompile(`on|off|optional|optional_no_ca`) authVerifyClientRegex = regexp.MustCompile(`on|off|optional|optional_no_ca`)
commonNameRegex = regexp.MustCompile(`^CN=[/\-.\_\~a-zA-Z0-9` + regexChars + `]*$`) commonNameRegex = regexp.MustCompile(`^CN=[/\-.\_\~a-zA-Z0-9` + regexChars + `]*$`)
redirectRegex = regexp.MustCompile(`^((https?://)?[A-Za-z0-9\-\.]*(:[0-9]+)?/[A-Za-z0-9\-\.]*)?$`) redirectRegex = regexp.MustCompile(`^((https?://)?[A-Za-z0-9\-.]*(:\d+)?/[A-Za-z0-9\-.]*)?$`)
) )
var authTLSAnnotations = parser.Annotation{ var authTLSAnnotations = parser.Annotation{
Group: "authentication", Group: "authentication",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
annotationAuthTLSSecret: { annotationAuthTLSSecret: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
Documentation: `This annotation defines the secret that contains the certificate chain of allowed certs`, Documentation: `This annotation defines the secret that contains the certificate chain of allowed certs`,
}, },
annotationAuthTLSVerifyClient: { annotationAuthTLSVerifyClient: {
Validator: parser.ValidateRegex(*authVerifyClientRegex, true), Validator: parser.ValidateRegex(authVerifyClientRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
Documentation: `This annotation enables verification of client certificates. Can be "on", "off", "optional" or "optional_no_ca"`, Documentation: `This annotation enables verification of client certificates. Can be "on", "off", "optional" or "optional_no_ca"`,
@ -70,7 +69,7 @@ var authTLSAnnotations = parser.Annotation{
Documentation: `This annotation defines validation depth between the provided client certificate and the Certification Authority chain.`, Documentation: `This annotation defines validation depth between the provided client certificate and the Certification Authority chain.`,
}, },
annotationAuthTLSErrorPage: { annotationAuthTLSErrorPage: {
Validator: parser.ValidateRegex(*redirectRegex, true), Validator: parser.ValidateRegex(redirectRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskHigh, Risk: parser.AnnotationRiskHigh,
Documentation: `This annotation defines the URL/Page that user should be redirected in case of a Certificate Authentication Error`, Documentation: `This annotation defines the URL/Page that user should be redirected in case of a Certificate Authentication Error`,
@ -82,7 +81,7 @@ var authTLSAnnotations = parser.Annotation{
Documentation: `This annotation defines if the received certificates should be passed or not to the upstream server in the header "ssl-client-cert"`, Documentation: `This annotation defines if the received certificates should be passed or not to the upstream server in the header "ssl-client-cert"`,
}, },
annotationAuthTLSMatchCN: { annotationAuthTLSMatchCN: {
Validator: parser.ValidateRegex(*commonNameRegex, true), Validator: parser.ValidateRegex(commonNameRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskHigh, Risk: parser.AnnotationRiskHigh,
Documentation: `This annotation adds a sanity check for the CN of the client certificate that is sent over using a string / regex starting with "CN="`, Documentation: `This annotation adds a sanity check for the CN of the client certificate that is sent over using a string / regex starting with "CN="`,
@ -130,9 +129,9 @@ func (assl1 *Config) Equal(assl2 *Config) bool {
} }
// NewParser creates a new TLS authentication annotation parser // NewParser creates a new TLS authentication annotation parser
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation { func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return authTLS{ return authTLS{
r: resolver, r: r,
annotationConfig: authTLSAnnotations, annotationConfig: authTLSAnnotations,
} }
} }
@ -169,7 +168,7 @@ func (a authTLS) Parse(ing *networking.Ingress) (interface{}, error) {
authCert, err := a.r.GetAuthCertificate(tlsauthsecret) authCert, err := a.r.GetAuthCertificate(tlsauthsecret)
if err != nil { if err != nil {
e := fmt.Errorf("error obtaining certificate: %w", err) e := fmt.Errorf("error obtaining certificate: %w", err)
return &Config{}, ing_errors.LocationDenied{Reason: e} return &Config{}, ing_errors.LocationDeniedError{Reason: e}
} }
config.AuthSSLCert = *authCert config.AuthSSLCert = *authCert

View file

@ -27,6 +27,11 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
const (
defaultDemoSecret = "default/demo-secret"
off = "off"
)
func buildIngress() *networking.Ingress { func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{ defaultBackend := networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
@ -77,23 +82,22 @@ type mockSecret struct {
// GetAuthCertificate from mockSecret mocks the GetAuthCertificate for authTLS // GetAuthCertificate from mockSecret mocks the GetAuthCertificate for authTLS
func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) { func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
if name != "default/demo-secret" { if name != defaultDemoSecret {
return nil, errors.Errorf("there is no secret with name %v", name) return nil, errors.Errorf("there is no secret with name %v", name)
} }
return &resolver.AuthSSLCert{ return &resolver.AuthSSLCert{
Secret: "default/demo-secret", Secret: defaultDemoSecret,
CAFileName: "/ssl/ca.crt", CAFileName: "/ssl/ca.crt",
CASHA: "abc", CASHA: "abc",
}, nil }, nil
} }
func TestAnnotations(t *testing.T) { func TestAnnotations(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(annotationAuthTLSSecret)] = "default/demo-secret" data[parser.GetAnnotationWithPrefix(annotationAuthTLSSecret)] = defaultDemoSecret
ing.SetAnnotations(data) ing.SetAnnotations(data)
@ -108,7 +112,7 @@ func TestAnnotations(t *testing.T) {
t.Errorf("expected *Config but got %v", u) t.Errorf("expected *Config but got %v", u)
} }
secret, err := fakeSecret.GetAuthCertificate("default/demo-secret") secret, err := fakeSecret.GetAuthCertificate(defaultDemoSecret)
if err != nil { if err != nil {
t.Errorf("unexpected error getting secret %v", err) t.Errorf("unexpected error getting secret %v", err)
} }
@ -132,7 +136,7 @@ func TestAnnotations(t *testing.T) {
t.Errorf("expected empty string, but got %v", u.MatchCN) t.Errorf("expected empty string, but got %v", u.MatchCN)
} }
data[parser.GetAnnotationWithPrefix(annotationAuthTLSVerifyClient)] = "off" data[parser.GetAnnotationWithPrefix(annotationAuthTLSVerifyClient)] = off
data[parser.GetAnnotationWithPrefix(annotationAuthTLSVerifyDepth)] = "2" data[parser.GetAnnotationWithPrefix(annotationAuthTLSVerifyDepth)] = "2"
data[parser.GetAnnotationWithPrefix(annotationAuthTLSErrorPage)] = "ok.com/error" data[parser.GetAnnotationWithPrefix(annotationAuthTLSErrorPage)] = "ok.com/error"
data[parser.GetAnnotationWithPrefix(annotationAuthTLSPassCertToUpstream)] = "true" data[parser.GetAnnotationWithPrefix(annotationAuthTLSPassCertToUpstream)] = "true"
@ -153,8 +157,8 @@ func TestAnnotations(t *testing.T) {
if u.AuthSSLCert.Secret != secret.Secret { if u.AuthSSLCert.Secret != secret.Secret {
t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret) t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret)
} }
if u.VerifyClient != "off" { if u.VerifyClient != off {
t.Errorf("expected %v but got %v", "off", u.VerifyClient) t.Errorf("expected %v but got %v", off, u.VerifyClient)
} }
if u.ValidationDepth != 2 { if u.ValidationDepth != 2 {
t.Errorf("expected %v but got %v", 2, u.ValidationDepth) t.Errorf("expected %v but got %v", 2, u.ValidationDepth)
@ -262,28 +266,21 @@ func TestInvalidAnnotations(t *testing.T) {
if u.MatchCN != "" { if u.MatchCN != "" {
t.Errorf("expected empty string but got %v", u.MatchCN) t.Errorf("expected empty string but got %v", u.MatchCN)
} }
} }
func TestEquals(t *testing.T) { func TestEquals(t *testing.T) {
cfg1 := &Config{} cfg1 := &Config{}
cfg2 := &Config{} cfg2 := &Config{}
// Same config
result := cfg1.Equal(cfg1)
if result != true {
t.Errorf("Expected true")
}
// compare nil // compare nil
result = cfg1.Equal(nil) result := cfg1.Equal(nil)
if result != false { if result != false {
t.Errorf("Expected false") t.Errorf("Expected false")
} }
// Different Certs // Different Certs
sslCert1 := resolver.AuthSSLCert{ sslCert1 := resolver.AuthSSLCert{
Secret: "default/demo-secret", Secret: defaultDemoSecret,
CAFileName: "/ssl/ca.crt", CAFileName: "/ssl/ca.crt",
CASHA: "abc", CASHA: "abc",
} }
@ -302,7 +299,7 @@ func TestEquals(t *testing.T) {
// Different Verify Client // Different Verify Client
cfg1.VerifyClient = "on" cfg1.VerifyClient = "on"
cfg2.VerifyClient = "off" cfg2.VerifyClient = off
result = cfg1.Equal(cfg2) result = cfg1.Equal(cfg2)
if result != false { if result != false {
t.Errorf("Expected false") t.Errorf("Expected false")

View file

@ -25,9 +25,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
var ( var validProtocols = []string{"auto_http", "http", "https", "grpc", "grpcs", "fcgi"}
validProtocols = []string{"auto_http", "http", "https", "grpc", "grpcs", "fcgi"}
)
const ( const (
http = "HTTP" http = "HTTP"

View file

@ -44,6 +44,7 @@ func buildIngress() *networking.Ingress {
}, },
} }
} }
func TestParseInvalidAnnotations(t *testing.T) { func TestParseInvalidAnnotations(t *testing.T) {
ing := buildIngress() ing := buildIngress()
@ -56,7 +57,7 @@ func TestParseInvalidAnnotations(t *testing.T) {
if !ok { if !ok {
t.Errorf("expected a string type") t.Errorf("expected a string type")
} }
if val != "HTTP" { if val != http {
t.Errorf("expected HTTPS but %v returned", val) t.Errorf("expected HTTPS but %v returned", val)
} }
@ -72,7 +73,7 @@ func TestParseInvalidAnnotations(t *testing.T) {
if !ok { if !ok {
t.Errorf("expected a string type") t.Errorf("expected a string type")
} }
if val != "HTTP" { if val != http {
t.Errorf("expected HTTPS but %v returned", val) t.Errorf("expected HTTPS but %v returned", val)
} }
@ -88,7 +89,7 @@ func TestParseInvalidAnnotations(t *testing.T) {
if !ok { if !ok {
t.Errorf("expected a string type") t.Errorf("expected a string type")
} }
if val != "HTTP" { if val != http {
t.Errorf("expected HTTPS but %v returned", val) t.Errorf("expected HTTPS but %v returned", val)
} }
} }

View file

@ -57,7 +57,7 @@ var CanaryAnnotations = parser.Annotation{
Documentation: `This annotation The total weight of traffic. If unspecified, it defaults to 100`, Documentation: `This annotation The total weight of traffic. If unspecified, it defaults to 100`,
}, },
canaryByHeaderAnnotation: { canaryByHeaderAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines the header that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress. Documentation: `This annotation defines the header that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress.
@ -65,7 +65,7 @@ var CanaryAnnotations = parser.Annotation{
For any other value, the header will be ignored and the request compared against the other canary rules by precedence`, For any other value, the header will be ignored and the request compared against the other canary rules by precedence`,
}, },
canaryByHeaderValueAnnotation: { canaryByHeaderValueAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines the header value to match for notifying the Ingress to route the request to the service specified in the Canary Ingress. Documentation: `This annotation defines the header value to match for notifying the Ingress to route the request to the service specified in the Canary Ingress.
@ -74,7 +74,7 @@ var CanaryAnnotations = parser.Annotation{
It doesn't have any effect if the 'canary-by-header' annotation is not defined`, It doesn't have any effect if the 'canary-by-header' annotation is not defined`,
}, },
canaryByHeaderPatternAnnotation: { canaryByHeaderPatternAnnotation: {
Validator: parser.ValidateRegex(*parser.IsValidRegex, false), Validator: parser.ValidateRegex(parser.IsValidRegex, false),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation works the same way as canary-by-header-value except it does PCRE Regex matching. Documentation: `This annotation works the same way as canary-by-header-value except it does PCRE Regex matching.
@ -82,7 +82,7 @@ var CanaryAnnotations = parser.Annotation{
When the given Regex causes error during request processing, the request will be considered as not matching.`, When the given Regex causes error during request processing, the request will be considered as not matching.`,
}, },
canaryByCookieAnnotation: { canaryByCookieAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines the cookie that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress. Documentation: `This annotation defines the cookie that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress.
@ -189,7 +189,7 @@ func (c canary) GetDocumentation() parser.AnnotationFields {
return c.annotationConfig.Annotations return c.annotationConfig.Annotations
} }
func (a canary) Validate(anns map[string]string) error { func (c canary) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(c.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, CanaryAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, CanaryAnnotations.Annotations)
} }

View file

@ -17,6 +17,7 @@ limitations under the License.
package canary package canary
import ( import (
"strconv"
"testing" "testing"
api "k8s.io/api/core/v1" api "k8s.io/api/core/v1"
@ -24,8 +25,6 @@ import (
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"strconv"
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
@ -93,7 +92,6 @@ func TestCanaryInvalid(t *testing.T) {
if val.Weight != 0 { if val.Weight != 0 {
t.Errorf("Expected %v but got %v", 0, val.Weight) t.Errorf("Expected %v but got %v", 0, val.Weight)
} }
} }
func TestAnnotations(t *testing.T) { func TestAnnotations(t *testing.T) {
@ -133,10 +131,9 @@ func TestAnnotations(t *testing.T) {
} }
continue continue
} else { }
if err != nil { if err != nil {
t.Errorf("%v: expected nil but returned error %v", test.title, err) t.Errorf("%v: expected nil but returned error %v", test.title, err)
}
} }
canaryConfig, ok := i.(*Config) canaryConfig, ok := i.(*Config)

View file

@ -31,7 +31,7 @@ var clientBodyBufferSizeConfig = parser.Annotation{
Group: "backend", Group: "backend",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
clientBodyBufferSizeAnnotation: { clientBodyBufferSizeAnnotation: {
Validator: parser.ValidateRegex(*parser.SizeRegex, true), Validator: parser.ValidateRegex(parser.SizeRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
Documentation: `Sets buffer size for reading client request body per location. Documentation: `Sets buffer size for reading client request body per location.
@ -65,7 +65,7 @@ func (cbbs clientBodyBufferSize) Parse(ing *networking.Ingress) (interface{}, er
return parser.GetStringAnnotation(clientBodyBufferSizeAnnotation, ing, cbbs.annotationConfig.Annotations) return parser.GetStringAnnotation(clientBodyBufferSizeAnnotation, ing, cbbs.annotationConfig.Annotations)
} }
func (a clientBodyBufferSize) Validate(anns map[string]string) error { func (cbbs clientBodyBufferSize) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(cbbs.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, clientBodyBufferSizeConfig.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, clientBodyBufferSizeConfig.Annotations)
} }

View file

@ -57,6 +57,7 @@ func TestParse(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations) ing.SetAnnotations(testCase.annotations)
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
result, _ := ap.Parse(ing) result, _ := ap.Parse(ing)
if result != testCase.expected { if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)

View file

@ -29,15 +29,13 @@ const (
connectionProxyHeaderAnnotation = "connection-proxy-header" connectionProxyHeaderAnnotation = "connection-proxy-header"
) )
var ( var validConnectionHeaderValue = regexp.MustCompile(`^(close|keep-alive)$`)
validConnectionHeaderValue = regexp.MustCompile(`^(close|keep-alive)$`)
)
var connectionHeadersAnnotations = parser.Annotation{ var connectionHeadersAnnotations = parser.Annotation{
Group: "backend", Group: "backend",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
connectionProxyHeaderAnnotation: { connectionProxyHeaderAnnotation: {
Validator: parser.ValidateRegex(*validConnectionHeaderValue, true), Validator: parser.ValidateRegex(validConnectionHeaderValue, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation allows setting a specific value for "proxy_set_header Connection" directive. Right now it is restricted to "close" or "keep-alive"`, Documentation: `This annotation allows setting a specific value for "proxy_set_header Connection" directive. Right now it is restricted to "close" or "keep-alive"`,

View file

@ -66,6 +66,5 @@ func TestParse(t *testing.T) {
if !p.Equal(testCase.expected) { if !p.Equal(testCase.expected) {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, p, testCase.annotations) t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, p, testCase.annotations)
} }
} }
} }

View file

@ -43,9 +43,9 @@ var (
// * Sets a group that can be (https?://)?*?.something.com:port? // * Sets a group that can be (https?://)?*?.something.com:port?
// * Allows this to be repeated as much as possible, and separated by comma // * Allows this to be repeated as much as possible, and separated by comma
// Otherwise it should be '*' // Otherwise it should be '*'
corsOriginRegexValidator = regexp.MustCompile(`^((((https?://)?(\*\.)?[A-Za-z0-9\-\.]*(:[0-9]+)?,?)+)|\*)?$`) corsOriginRegexValidator = regexp.MustCompile(`^((((https?://)?(\*\.)?[A-Za-z0-9\-.]*(:\d+)?,?)+)|\*)?$`)
// corsOriginRegex defines the regex for validation inside Parse // corsOriginRegex defines the regex for validation inside Parse
corsOriginRegex = regexp.MustCompile(`^(https?://(\*\.)?[A-Za-z0-9\-\.]*(:[0-9]+)?|\*)?$`) corsOriginRegex = regexp.MustCompile(`^(https?://(\*\.)?[A-Za-z0-9\-.]*(:\d+)?|\*)?$`)
// Method must contain valid methods list (PUT, GET, POST, BLA) // Method must contain valid methods list (PUT, GET, POST, BLA)
// May contain or not spaces between each verb // May contain or not spaces between each verb
corsMethodsRegex = regexp.MustCompile(`^([A-Za-z]+,?\s?)+$`) corsMethodsRegex = regexp.MustCompile(`^([A-Za-z]+,?\s?)+$`)
@ -74,7 +74,7 @@ var corsAnnotation = parser.Annotation{
Documentation: `This annotation enables Cross-Origin Resource Sharing (CORS) in an Ingress rule`, Documentation: `This annotation enables Cross-Origin Resource Sharing (CORS) in an Ingress rule`,
}, },
corsAllowOriginAnnotation: { corsAllowOriginAnnotation: {
Validator: parser.ValidateRegex(*corsOriginRegexValidator, true), Validator: parser.ValidateRegex(corsOriginRegexValidator, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation controls what's the accepted Origin for CORS. Documentation: `This annotation controls what's the accepted Origin for CORS.
@ -82,14 +82,14 @@ var corsAnnotation = parser.Annotation{
It also supports single level wildcard subdomains and follows this format: http(s)://*.foo.bar, http(s)://*.bar.foo:8080 or http(s)://*.abc.bar.foo:9000`, It also supports single level wildcard subdomains and follows this format: http(s)://*.foo.bar, http(s)://*.bar.foo:8080 or http(s)://*.abc.bar.foo:9000`,
}, },
corsAllowHeadersAnnotation: { corsAllowHeadersAnnotation: {
Validator: parser.ValidateRegex(*parser.HeadersVariable, true), Validator: parser.ValidateRegex(parser.HeadersVariable, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation controls which headers are accepted. Documentation: `This annotation controls which headers are accepted.
This is a multi-valued field, separated by ',' and accepts letters, numbers, _ and -`, This is a multi-valued field, separated by ',' and accepts letters, numbers, _ and -`,
}, },
corsAllowMethodsAnnotation: { corsAllowMethodsAnnotation: {
Validator: parser.ValidateRegex(*corsMethodsRegex, true), Validator: parser.ValidateRegex(corsMethodsRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation controls which methods are accepted. Documentation: `This annotation controls which methods are accepted.
@ -102,7 +102,7 @@ var corsAnnotation = parser.Annotation{
Documentation: `This annotation controls if credentials can be passed during CORS operations.`, Documentation: `This annotation controls if credentials can be passed during CORS operations.`,
}, },
corsExposeHeadersAnnotation: { corsExposeHeadersAnnotation: {
Validator: parser.ValidateRegex(*corsExposeHeadersRegex, true), Validator: parser.ValidateRegex(corsExposeHeadersRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation controls which headers are exposed to response. Documentation: `This annotation controls which headers are exposed to response.
@ -260,7 +260,7 @@ func (c cors) GetDocumentation() parser.AnnotationFields {
return c.annotationConfig.Annotations return c.annotationConfig.Annotations
} }
func (a cors) Validate(anns map[string]string) error { func (c cors) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(c.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, corsAnnotation.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, corsAnnotation.Annotations)
} }

View file

@ -31,16 +31,14 @@ const (
customHTTPErrorsAnnotation = "custom-http-errors" customHTTPErrorsAnnotation = "custom-http-errors"
) )
var ( // We accept anything between 400 and 599, on a comma separated.
// We accept anything between 400 and 599, on a comma separated. var arrayOfHTTPErrors = regexp.MustCompile(`^(?:[4,5]\d{2},?)*$`)
arrayOfHTTPErrors = regexp.MustCompile(`^(?:[4,5][0-9][0-9],?)*$`)
)
var customHTTPErrorsAnnotations = parser.Annotation{ var customHTTPErrorsAnnotations = parser.Annotation{
Group: "backend", Group: "backend",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
customHTTPErrorsAnnotation: { customHTTPErrorsAnnotation: {
Validator: parser.ValidateRegex(*arrayOfHTTPErrors, true), Validator: parser.ValidateRegex(arrayOfHTTPErrors, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `If a default backend annotation is specified on the ingress, the errors code specified on this annotation Documentation: `If a default backend annotation is specified on the ingress, the errors code specified on this annotation
@ -72,7 +70,7 @@ func (e customhttperrors) Parse(ing *networking.Ingress) (interface{}, error) {
} }
cSplit := strings.Split(c, ",") cSplit := strings.Split(c, ",")
var codes []int codes := make([]int, 0, len(cSplit))
for _, i := range cSplit { for _, i := range cSplit {
num, err := strconv.Atoi(i) num, err := strconv.Atoi(i)
if err != nil { if err != nil {
@ -88,7 +86,7 @@ func (e customhttperrors) GetDocumentation() parser.AnnotationFields {
return e.annotationConfig.Annotations return e.annotationConfig.Annotations
} }
func (a customhttperrors) Validate(anns map[string]string) error { func (e customhttperrors) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(e.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, customHTTPErrorsAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, customHTTPErrorsAnnotations.Annotations)
} }

View file

@ -57,14 +57,14 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress to use // Parse parses the annotations contained in the ingress to use
// a custom default backend // a custom default backend
func (db backend) Parse(ing *networking.Ingress) (interface{}, error) { func (b backend) Parse(ing *networking.Ingress) (interface{}, error) {
s, err := parser.GetStringAnnotation(defaultBackendAnnotation, ing, db.annotationConfig.Annotations) s, err := parser.GetStringAnnotation(defaultBackendAnnotation, ing, b.annotationConfig.Annotations)
if err != nil { if err != nil {
return nil, err return nil, err
} }
name := fmt.Sprintf("%v/%v", ing.Namespace, s) name := fmt.Sprintf("%v/%v", ing.Namespace, s)
svc, err := db.r.GetService(name) svc, err := b.r.GetService(name)
if err != nil { if err != nil {
return nil, fmt.Errorf("unexpected error reading service %s: %w", name, err) return nil, fmt.Errorf("unexpected error reading service %s: %w", name, err)
} }
@ -72,11 +72,11 @@ func (db backend) Parse(ing *networking.Ingress) (interface{}, error) {
return svc, nil return svc, nil
} }
func (db backend) GetDocumentation() parser.AnnotationFields { func (b backend) GetDocumentation() parser.AnnotationFields {
return db.annotationConfig.Annotations return b.annotationConfig.Annotations
} }
func (a backend) Validate(anns map[string]string) error { func (b backend) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(b.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, defaultBackendAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, defaultBackendAnnotations.Annotations)
} }

View file

@ -35,22 +35,20 @@ const (
fastCGIParamsAnnotation = "fastcgi-params-configmap" fastCGIParamsAnnotation = "fastcgi-params-configmap"
) )
var ( // fast-cgi valid parameters is just a single file name (like index.php)
// fast-cgi valid parameters is just a single file name (like index.php) var regexValidIndexAnnotationAndKey = regexp.MustCompile(`^[A-Za-z0-9.\-\_]+$`)
regexValidIndexAnnotationAndKey = regexp.MustCompile(`^[A-Za-z0-9\.\-\_]+$`)
)
var fastCGIAnnotations = parser.Annotation{ var fastCGIAnnotations = parser.Annotation{
Group: "fastcgi", Group: "fastcgi",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
fastCGIIndexAnnotation: { fastCGIIndexAnnotation: {
Validator: parser.ValidateRegex(*regexValidIndexAnnotationAndKey, true), Validator: parser.ValidateRegex(regexValidIndexAnnotationAndKey, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation can be used to specify an index file`, Documentation: `This annotation can be used to specify an index file`,
}, },
fastCGIParamsAnnotation: { fastCGIParamsAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation can be used to specify a ConfigMap containing the fastcgi parameters as a key/value. Documentation: `This annotation can be used to specify a ConfigMap containing the fastcgi parameters as a key/value.
@ -98,7 +96,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress // ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate the fastcgiConfig. // rule used to indicate the fastcgiConfig.
func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) { func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
fcgiConfig := Config{} fcgiConfig := Config{}
if ing.GetAnnotations() == nil { if ing.GetAnnotations() == nil {
@ -125,7 +122,7 @@ func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
cmns, cmn, err := cache.SplitMetaNamespaceKey(cm) cmns, cmn, err := cache.SplitMetaNamespaceKey(cm)
if err != nil { if err != nil {
return fcgiConfig, ing_errors.LocationDenied{ return fcgiConfig, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("error reading configmap name from annotation: %w", err), Reason: fmt.Errorf("error reading configmap name from annotation: %w", err),
} }
} }
@ -139,7 +136,7 @@ func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
cm = fmt.Sprintf("%v/%v", ing.Namespace, cmn) cm = fmt.Sprintf("%v/%v", ing.Namespace, cmn)
cmap, err := a.r.GetConfigMap(cm) cmap, err := a.r.GetConfigMap(cm)
if err != nil { if err != nil {
return fcgiConfig, ing_errors.LocationDenied{ return fcgiConfig, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("unexpected error reading configmap %s: %w", cm, err), Reason: fmt.Errorf("unexpected error reading configmap %s: %w", cm, err),
} }
} }

View file

@ -155,7 +155,6 @@ func TestParseFastCGIInvalidParamsConfigMapAnnotation(t *testing.T) {
invalidConfigMapList := []string{"unknown/configMap", "unknown/config/map"} invalidConfigMapList := []string{"unknown/configMap", "unknown/config/map"}
for _, configmap := range invalidConfigMapList { for _, configmap := range invalidConfigMapList {
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = configmap data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = configmap
ing.SetAnnotations(data) ing.SetAnnotations(data)
@ -239,11 +238,9 @@ func TestParseFastCGIParamsConfigMapAnnotationWithDifferentNS(t *testing.T) {
if err == nil { if err == nil {
t.Errorf("Different namespace configmap should return an error") t.Errorf("Different namespace configmap should return an error")
} }
} }
func TestConfigEquality(t *testing.T) { func TestConfigEquality(t *testing.T) {
var nilConfig *Config var nilConfig *Config
config := Config{ config := Config{
@ -297,7 +294,6 @@ func TestConfigEquality(t *testing.T) {
} }
func Test_fastcgi_Parse(t *testing.T) { func Test_fastcgi_Parse(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
index string index string
@ -378,7 +374,6 @@ func Test_fastcgi_Parse(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}

View file

@ -25,7 +25,6 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors" ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
"k8s.io/ingress-nginx/internal/net" "k8s.io/ingress-nginx/internal/net"
@ -57,7 +56,7 @@ var globalRateLimitAnnotationConfig = parser.Annotation{
Documentation: `Configures a time window (i.e 1m) that the limit is applied`, Documentation: `Configures a time window (i.e 1m) that the limit is applied`,
}, },
globalRateLimitKeyAnnotation: { globalRateLimitKeyAnnotation: {
Validator: parser.ValidateRegex(*parser.NGINXVariable, true), Validator: parser.ValidateRegex(parser.NGINXVariable, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskHigh, Risk: parser.AnnotationRiskHigh,
Documentation: `This annotation Configures a key for counting the samples. Defaults to $remote_addr. Documentation: `This annotation Configures a key for counting the samples. Defaults to $remote_addr.
@ -123,23 +122,23 @@ func (a globalratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
config := &Config{} config := &Config{}
limit, err := parser.GetIntAnnotation(globalRateLimitAnnotation, ing, a.annotationConfig.Annotations) limit, err := parser.GetIntAnnotation(globalRateLimitAnnotation, ing, a.annotationConfig.Annotations)
if err != nil && errors.IsInvalidContent(err) { if err != nil && ing_errors.IsInvalidContent(err) {
return nil, err return nil, err
} }
rawWindowSize, err := parser.GetStringAnnotation(globalRateLimitWindowAnnotation, ing, a.annotationConfig.Annotations) rawWindowSize, err := parser.GetStringAnnotation(globalRateLimitWindowAnnotation, ing, a.annotationConfig.Annotations)
if err != nil && errors.IsValidationError(err) { if err != nil && ing_errors.IsValidationError(err) {
return config, ing_errors.LocationDenied{ return config, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("failed to parse 'global-rate-limit-window' value: %w", err), Reason: fmt.Errorf("failed to parse 'global-rate-limit-window' value: %w", err),
} }
} }
if limit == 0 || len(rawWindowSize) == 0 { if limit == 0 || rawWindowSize == "" {
return config, nil return config, nil
} }
windowSize, err := time.ParseDuration(rawWindowSize) windowSize, err := time.ParseDuration(rawWindowSize)
if err != nil { if err != nil {
return config, ing_errors.LocationDenied{ return config, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("failed to parse 'global-rate-limit-window' value: %w", err), Reason: fmt.Errorf("failed to parse 'global-rate-limit-window' value: %w", err),
} }
} }
@ -148,12 +147,12 @@ func (a globalratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
if err != nil { if err != nil {
klog.Warningf("invalid %s, defaulting to %s", globalRateLimitKeyAnnotation, defaultKey) klog.Warningf("invalid %s, defaulting to %s", globalRateLimitKeyAnnotation, defaultKey)
} }
if len(key) == 0 { if key == "" {
key = defaultKey key = defaultKey
} }
rawIgnoredCIDRs, err := parser.GetStringAnnotation(globalRateLimitIgnoredCidrsAnnotation, ing, a.annotationConfig.Annotations) rawIgnoredCIDRs, err := parser.GetStringAnnotation(globalRateLimitIgnoredCidrsAnnotation, ing, a.annotationConfig.Annotations)
if err != nil && errors.IsInvalidContent(err) { if err != nil && ing_errors.IsInvalidContent(err) {
return nil, err return nil, err
} }
ignoredCIDRs, err := net.ParseCIDRs(rawIgnoredCIDRs) ignoredCIDRs, err := net.ParseCIDRs(rawIgnoredCIDRs)
@ -161,7 +160,7 @@ func (a globalratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
return nil, err return nil, err
} }
config.Namespace = strings.Replace(string(ing.UID), "-", "", -1) config.Namespace = strings.ReplaceAll(string(ing.UID), "-", "")
config.Limit = limit config.Limit = limit
config.WindowSize = int(windowSize.Seconds()) config.WindowSize = int(windowSize.Seconds())
config.Key = key config.Key = key

View file

@ -30,8 +30,10 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
const UID = "31285d47-b150-4dcf-bd6f-12c46d769f6e" const (
const expectedUID = "31285d47b1504dcfbd6f12c46d769f6e" UID = "31285d47-b150-4dcf-bd6f-12c46d769f6e"
expectedUID = "31285d47b1504dcfbd6f12c46d769f6e"
)
func buildIngress() *networking.Ingress { func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{ defaultBackend := networking.IngressBackend{
@ -190,10 +192,19 @@ func TestGlobalRateLimiting(t *testing.T) {
t.Errorf("expected error '%v' but got '%v'", testCase.expectedErr, actualErr) t.Errorf("expected error '%v' but got '%v'", testCase.expectedErr, actualErr)
} }
actualConfig := i.(*Config) actualConfig, ok := i.(*Config)
if !ok {
t.Errorf("expected Config type but got %T", i)
}
if !testCase.expectedConfig.Equal(actualConfig) { if !testCase.expectedConfig.Equal(actualConfig) {
expectedJSON, _ := json.Marshal(testCase.expectedConfig) expectedJSON, err := json.Marshal(testCase.expectedConfig)
actualJSON, _ := json.Marshal(actualConfig) if err != nil {
t.Errorf("failed to marshal expected config: %v", err)
}
actualJSON, err := json.Marshal(actualConfig)
if err != nil {
t.Errorf("failed to marshal actual config: %v", err)
}
t.Errorf("%v: expected config '%s' but got '%s'", testCase.title, expectedJSON, actualJSON) t.Errorf("%v: expected config '%s' but got '%s'", testCase.title, expectedJSON, actualJSON)
} }
} }

View file

@ -62,7 +62,7 @@ func (h2pp http2PushPreload) GetDocumentation() parser.AnnotationFields {
return h2pp.annotationConfig.Annotations return h2pp.annotationConfig.Annotations
} }
func (a http2PushPreload) Validate(anns map[string]string) error { func (h2pp http2PushPreload) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(h2pp.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, http2PushPreloadAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, http2PushPreloadAnnotations.Annotations)
} }

View file

@ -96,16 +96,15 @@ func (a ipallowlist) Parse(ing *networking.Ingress) (interface{}, error) {
return &SourceRange{CIDR: defaultAllowlistSourceRange}, nil return &SourceRange{CIDR: defaultAllowlistSourceRange}, nil
} }
return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDenied{ return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDeniedError{
Reason: err, Reason: err,
} }
} }
values := strings.Split(val, ",") values := strings.Split(val, ",")
ipnets, ips, err := net.ParseIPNets(values...) ipnets, ips, err := net.ParseIPNets(values...)
if err != nil && len(ips) == 0 { if err != nil && len(ips) == 0 {
return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDenied{ return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("the annotation does not contain a valid IP address or network: %w", err), Reason: fmt.Errorf("the annotation does not contain a valid IP address or network: %w", err),
} }
} }

View file

@ -93,16 +93,15 @@ func (a ipdenylist) Parse(ing *networking.Ingress) (interface{}, error) {
return &SourceRange{CIDR: defaultDenylistSourceRange}, nil return &SourceRange{CIDR: defaultDenylistSourceRange}, nil
} }
return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDenied{ return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDeniedError{
Reason: err, Reason: err,
} }
} }
values := strings.Split(val, ",") values := strings.Split(val, ",")
ipnets, ips, err := net.ParseIPNets(values...) ipnets, ips, err := net.ParseIPNets(values...)
if err != nil && len(ips) == 0 { if err != nil && len(ips) == 0 {
return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDenied{ return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("the annotation does not contain a valid IP address or network: %w", err), Reason: fmt.Errorf("the annotation does not contain a valid IP address or network: %w", err),
} }
} }

View file

@ -54,6 +54,7 @@ func TestParse(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations) ing.SetAnnotations(testCase.annotations)
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
result, _ := ap.Parse(ing) result, _ := ap.Parse(ing)
if result != testCase.expected { if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)

View file

@ -101,7 +101,7 @@ func (l log) GetDocumentation() parser.AnnotationFields {
return l.annotationConfig.Annotations return l.annotationConfig.Annotations
} }
func (a log) Validate(anns map[string]string) error { func (l log) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(l.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, logAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, logAnnotations.Annotations)
} }

View file

@ -76,7 +76,10 @@ func TestIngressAccessLogConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(enableAccessLogAnnotation)] = "false" data[parser.GetAnnotationWithPrefix(enableAccessLogAnnotation)] = "false"
ing.SetAnnotations(data) ing.SetAnnotations(data)
log, _ := NewParser(&resolver.Mock{}).Parse(ing) log, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
nginxLogs, ok := log.(*Config) nginxLogs, ok := log.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")
@ -94,7 +97,10 @@ func TestIngressRewriteLogConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(enableRewriteLogAnnotation)] = "true" data[parser.GetAnnotationWithPrefix(enableRewriteLogAnnotation)] = "true"
ing.SetAnnotations(data) ing.SetAnnotations(data)
log, _ := NewParser(&resolver.Mock{}).Parse(ing) log, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing annotations %v", err)
}
nginxLogs, ok := log.(*Config) nginxLogs, ok := log.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")
@ -112,7 +118,10 @@ func TestInvalidBoolConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(enableRewriteLogAnnotation)] = "blo" data[parser.GetAnnotationWithPrefix(enableRewriteLogAnnotation)] = "blo"
ing.SetAnnotations(data) ing.SetAnnotations(data)
log, _ := NewParser(&resolver.Mock{}).Parse(ing) log, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
nginxLogs, ok := log.(*Config) nginxLogs, ok := log.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")

View file

@ -34,15 +34,13 @@ const (
mirrorHostAnnotation = "mirror-host" mirrorHostAnnotation = "mirror-host"
) )
var ( var OnOffRegex = regexp.MustCompile(`^(on|off)$`)
OnOffRegex = regexp.MustCompile(`^(on|off)$`)
)
var mirrorAnnotation = parser.Annotation{ var mirrorAnnotation = parser.Annotation{
Group: "mirror", Group: "mirror",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
mirrorRequestBodyAnnotation: { mirrorRequestBodyAnnotation: {
Validator: parser.ValidateRegex(*OnOffRegex, true), Validator: parser.ValidateRegex(OnOffRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation defines if the request-body should be sent to the mirror backend. Can be 'on' or 'off'`, Documentation: `This annotation defines if the request-body should be sent to the mirror backend. Can be 'on' or 'off'`,

View file

@ -27,7 +27,7 @@ import (
const ( const (
modsecEnableAnnotation = "enable-modsecurity" modsecEnableAnnotation = "enable-modsecurity"
modsecEnableOwaspCoreAnnotation = "enable-owasp-core-rules" modsecEnableOwaspCoreAnnotation = "enable-owasp-core-rules"
modesecTransactionIdAnnotation = "modsecurity-transaction-id" modesecTransactionIDAnnotation = "modsecurity-transaction-id"
modsecSnippetAnnotation = "modsecurity-snippet" modsecSnippetAnnotation = "modsecurity-snippet"
) )
@ -46,8 +46,8 @@ var modsecurityAnnotation = parser.Annotation{
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation enables the OWASP Core Rule Set`, Documentation: `This annotation enables the OWASP Core Rule Set`,
}, },
modesecTransactionIdAnnotation: { modesecTransactionIDAnnotation: {
Validator: parser.ValidateRegex(*parser.NGINXVariable, true), Validator: parser.ValidateRegex(parser.NGINXVariable, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskHigh, Risk: parser.AnnotationRiskHigh,
Documentation: `This annotation enables passing an NGINX variable to ModSecurity.`, Documentation: `This annotation enables passing an NGINX variable to ModSecurity.`,
@ -98,9 +98,9 @@ func (modsec1 *Config) Equal(modsec2 *Config) bool {
} }
// NewParser creates a new ModSecurity annotation parser // NewParser creates a new ModSecurity annotation parser
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation { func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return modSecurity{ return modSecurity{
r: resolver, r: r,
annotationConfig: modsecurityAnnotation, annotationConfig: modsecurityAnnotation,
} }
} }
@ -134,10 +134,10 @@ func (a modSecurity) Parse(ing *networking.Ingress) (interface{}, error) {
config.OWASPRules = false config.OWASPRules = false
} }
config.TransactionID, err = parser.GetStringAnnotation(modesecTransactionIdAnnotation, ing, a.annotationConfig.Annotations) config.TransactionID, err = parser.GetStringAnnotation(modesecTransactionIDAnnotation, ing, a.annotationConfig.Annotations)
if err != nil { if err != nil {
if errors.IsInvalidContent(err) { if errors.IsInvalidContent(err) {
klog.Warningf("annotation %s contains invalid directive, defaulting", modesecTransactionIdAnnotation) klog.Warningf("annotation %s contains invalid directive, defaulting", modesecTransactionIDAnnotation)
} }
config.TransactionID = "" config.TransactionID = ""
} }

View file

@ -69,8 +69,14 @@ func TestParse(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations) ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing) result, err := ap.Parse(ing)
config := result.(*Config) if err != nil {
t.Errorf("unexpected error: %v", err)
}
config, ok := result.(*Config)
if !ok {
t.Errorf("unexpected type: %T", result)
}
if !config.Equal(&testCase.expected) { if !config.Equal(&testCase.expected) {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
} }

View file

@ -51,7 +51,7 @@ var otelAnnotations = parser.Annotation{
Documentation: `This annotation enables or disables using spans from incoming requests as parent for created ones`, Documentation: `This annotation enables or disables using spans from incoming requests as parent for created ones`,
}, },
otelOperationNameAnnotation: { otelOperationNameAnnotation: {
Validator: parser.ValidateRegex(*regexOperationName, true), Validator: parser.ValidateRegex(regexOperationName, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines what operation name should be added to the span`, Documentation: `This annotation defines what operation name should be added to the span`,
@ -75,7 +75,6 @@ type Config struct {
// Equal tests for equality between two Config types // Equal tests for equality between two Config types
func (bd1 *Config) Equal(bd2 *Config) bool { func (bd1 *Config) Equal(bd2 *Config) bool {
if bd1.Set != bd2.Set { if bd1.Set != bd2.Set {
return false return false
} }
@ -150,7 +149,7 @@ func (c opentelemetry) GetDocumentation() parser.AnnotationFields {
return c.annotationConfig.Annotations return c.annotationConfig.Annotations
} }
func (a opentelemetry) Validate(anns map[string]string) error { func (c opentelemetry) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(c.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, otelAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, otelAnnotations.Annotations)
} }

View file

@ -26,6 +26,8 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
const enableAnnotation = "true"
func buildIngress() *networking.Ingress { func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{ defaultBackend := networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
@ -73,10 +75,13 @@ func TestIngressAnnotationOpentelemetrySetTrue(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = "true" data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = enableAnnotation
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing) val, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
openTelemetry, ok := val.(*Config) openTelemetry, ok := val.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")
@ -103,7 +108,10 @@ func TestIngressAnnotationOpentelemetrySetFalse(t *testing.T) {
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = "false" data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = "false"
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing) val, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
openTelemetry, ok := val.(*Config) openTelemetry, ok := val.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")
@ -123,8 +131,8 @@ func TestIngressAnnotationOpentelemetryTrustSetTrue(t *testing.T) {
data := map[string]string{} data := map[string]string{}
opName := "foo-op" opName := "foo-op"
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = "true" data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = enableAnnotation
data[parser.GetAnnotationWithPrefix(otelTrustSpanAnnotation)] = "true" data[parser.GetAnnotationWithPrefix(otelTrustSpanAnnotation)] = enableAnnotation
data[parser.GetAnnotationWithPrefix(otelOperationNameAnnotation)] = opName data[parser.GetAnnotationWithPrefix(otelOperationNameAnnotation)] = opName
ing.SetAnnotations(data) ing.SetAnnotations(data)
@ -163,7 +171,7 @@ func TestIngressAnnotationOpentelemetryWithBadOpName(t *testing.T) {
data := map[string]string{} data := map[string]string{}
opName := "fooxpto_123$la;" opName := "fooxpto_123$la;"
data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = "true" data[parser.GetAnnotationWithPrefix(enableOpenTelemetryAnnotation)] = enableAnnotation
data[parser.GetAnnotationWithPrefix(otelOperationNameAnnotation)] = opName data[parser.GetAnnotationWithPrefix(otelOperationNameAnnotation)] = opName
ing.SetAnnotations(data) ing.SetAnnotations(data)
@ -180,7 +188,10 @@ func TestIngressAnnotationOpentelemetryUnset(t *testing.T) {
data := map[string]string{} data := map[string]string{}
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing) val, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
_, ok := val.(*Config) _, ok := val.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")

View file

@ -89,13 +89,13 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
} }
} }
func (s opentracing) Parse(ing *networking.Ingress) (interface{}, error) { func (o opentracing) Parse(ing *networking.Ingress) (interface{}, error) {
enabled, err := parser.GetBoolAnnotation(enableOpentracingAnnotation, ing, s.annotationConfig.Annotations) enabled, err := parser.GetBoolAnnotation(enableOpentracingAnnotation, ing, o.annotationConfig.Annotations)
if err != nil { if err != nil {
return &Config{}, nil return &Config{}, nil
} }
trustSpan, err := parser.GetBoolAnnotation(opentracingTrustSpanAnnotation, ing, s.annotationConfig.Annotations) trustSpan, err := parser.GetBoolAnnotation(opentracingTrustSpanAnnotation, ing, o.annotationConfig.Annotations)
if err != nil { if err != nil {
return &Config{Set: true, Enabled: enabled}, nil return &Config{Set: true, Enabled: enabled}, nil
} }
@ -103,11 +103,11 @@ func (s opentracing) Parse(ing *networking.Ingress) (interface{}, error) {
return &Config{Set: true, Enabled: enabled, TrustSet: true, TrustEnabled: trustSpan}, nil return &Config{Set: true, Enabled: enabled, TrustSet: true, TrustEnabled: trustSpan}, nil
} }
func (s opentracing) GetDocumentation() parser.AnnotationFields { func (o opentracing) GetDocumentation() parser.AnnotationFields {
return s.annotationConfig.Annotations return o.annotationConfig.Annotations
} }
func (a opentracing) Validate(anns map[string]string) error { func (o opentracing) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(o.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, opentracingAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, opentracingAnnotations.Annotations)
} }

View file

@ -26,6 +26,8 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
const enableAnnotation = "true"
func buildIngress() *networking.Ingress { func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{ defaultBackend := networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
@ -73,10 +75,13 @@ func TestIngressAnnotationOpentracingSetTrue(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "true" data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = enableAnnotation
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing) val, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error %v", err)
}
openTracing, ok := val.(*Config) openTracing, ok := val.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")
@ -95,7 +100,10 @@ func TestIngressAnnotationOpentracingSetFalse(t *testing.T) {
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "false" data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "false"
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing) val, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error %v", err)
}
openTracing, ok := val.(*Config) openTracing, ok := val.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")
@ -110,11 +118,14 @@ func TestIngressAnnotationOpentracingTrustSetTrue(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "true" data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = enableAnnotation
data[parser.GetAnnotationWithPrefix(opentracingTrustSpanAnnotation)] = "true" data[parser.GetAnnotationWithPrefix(opentracingTrustSpanAnnotation)] = enableAnnotation
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing) val, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error %v", err)
}
openTracing, ok := val.(*Config) openTracing, ok := val.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")
@ -136,7 +147,11 @@ func TestIngressAnnotationOpentracingUnset(t *testing.T) {
data := map[string]string{} data := map[string]string{}
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing) val, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
_, ok := val.(*Config) _, ok := val.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")

View file

@ -118,7 +118,7 @@ func (a ingAnnotations) parseString(name string) (string, error) {
val, ok := a[name] val, ok := a[name]
if ok { if ok {
s := normalizeString(val) s := normalizeString(val)
if len(s) == 0 { if s == "" {
return "", errors.NewInvalidAnnotationContent(name, val) return "", errors.NewInvalidAnnotationContent(name, val)
} }
@ -248,13 +248,14 @@ func StringToURL(input string) (*url.URL, error) {
return nil, fmt.Errorf("%v is not a valid URL: %v", input, err) return nil, fmt.Errorf("%v is not a valid URL: %v", input, err)
} }
if parsedURL.Scheme == "" { switch {
case parsedURL.Scheme == "":
return nil, fmt.Errorf("url scheme is empty") return nil, fmt.Errorf("url scheme is empty")
} else if parsedURL.Host == "" { case parsedURL.Host == "":
return nil, fmt.Errorf("url host is empty") return nil, fmt.Errorf("url host is empty")
} else if strings.Contains(parsedURL.Host, "..") { case strings.Contains(parsedURL.Host, ".."):
return nil, fmt.Errorf("invalid url host") return nil, fmt.Errorf("invalid url host")
default:
return parsedURL, nil
} }
return parsedURL, nil
} }

View file

@ -93,14 +93,16 @@ func TestGetStringAnnotation(t *testing.T) {
{"valid - A", "string", "A ", "A", false}, {"valid - A", "string", "A ", "A", false},
{"valid - B", "string", " B", "B", false}, {"valid - B", "string", " B", "B", false},
{"empty", "string", " ", "", true}, {"empty", "string", " ", "", true},
{"valid multiline", "string", ` {
"valid multiline", "string", `
rewrite (?i)/arcgis/rest/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/rest/services/Utilities/Geometry/GeometryServer$1 break; rewrite (?i)/arcgis/rest/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/rest/services/Utilities/Geometry/GeometryServer$1 break;
rewrite (?i)/arcgis/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break; rewrite (?i)/arcgis/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break;
`, ` `, `
rewrite (?i)/arcgis/rest/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/rest/services/Utilities/Geometry/GeometryServer$1 break; rewrite (?i)/arcgis/rest/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/rest/services/Utilities/Geometry/GeometryServer$1 break;
rewrite (?i)/arcgis/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break; rewrite (?i)/arcgis/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break;
`, `,
false}, false,
},
} }
data := map[string]string{} data := map[string]string{}
@ -213,8 +215,10 @@ func TestGetIntAnnotation(t *testing.T) {
func TestStringToURL(t *testing.T) { func TestStringToURL(t *testing.T) {
validURL := "http://bar.foo.com/external-auth" validURL := "http://bar.foo.com/external-auth"
validParsedURL, _ := url.Parse(validURL) validParsedURL, err := url.Parse(validURL)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tests := []struct { tests := []struct {
title string title string
url string url string

View file

@ -52,7 +52,7 @@ var (
var IsValidRegex = regexp.MustCompile("^[/" + alphaNumericChars + regexEnabledChars + "]*$") var IsValidRegex = regexp.MustCompile("^[/" + alphaNumericChars + regexEnabledChars + "]*$")
// SizeRegex validates sizes understood by NGINX, like 1000, 100k, 1000M // SizeRegex validates sizes understood by NGINX, like 1000, 100k, 1000M
var SizeRegex = regexp.MustCompile("^(?i)[0-9]+[bkmg]?$") var SizeRegex = regexp.MustCompile(`^(?i)\d+[bkmg]?$`)
// URLRegex is used to validate a URL but with only a specific set of characters: // URLRegex is used to validate a URL but with only a specific set of characters:
// It is alphanumericChar + ":", "?", "&" // It is alphanumericChar + ":", "?", "&"
@ -103,7 +103,7 @@ func ValidateServerName(value string) error {
// ValidateRegex receives a regex as an argument and uses it to validate // ValidateRegex receives a regex as an argument and uses it to validate
// the value of the field. // the value of the field.
// Annotation can define if the spaces should be trimmed before validating the value // Annotation can define if the spaces should be trimmed before validating the value
func ValidateRegex(regex regexp.Regexp, removeSpace bool) AnnotationValidator { func ValidateRegex(regex *regexp.Regexp, removeSpace bool) AnnotationValidator {
return func(s string) error { return func(s string) error {
if removeSpace { if removeSpace {
s = strings.ReplaceAll(s, " ", "") s = strings.ReplaceAll(s, " ", "")
@ -117,7 +117,7 @@ func ValidateRegex(regex regexp.Regexp, removeSpace bool) AnnotationValidator {
// ValidateOptions receives an array of valid options that can be the value of annotation. // ValidateOptions receives an array of valid options that can be the value of annotation.
// If no valid option is found, it will return an error // If no valid option is found, it will return an error
func ValidateOptions(options []string, caseSensitive bool, trimSpace bool) AnnotationValidator { func ValidateOptions(options []string, caseSensitive, trimSpace bool) AnnotationValidator {
return func(s string) error { return func(s string) error {
if trimSpace { if trimSpace {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
@ -161,7 +161,7 @@ func ValidateDuration(value string) error {
// ValidateNull always return null values and should not be widely used. // ValidateNull always return null values and should not be widely used.
// It is used on the "snippet" annotations, as it is up to the admin to allow its // It is used on the "snippet" annotations, as it is up to the admin to allow its
// usage, knowing it can be critical! // usage, knowing it can be critical!
func ValidateNull(value string) error { func ValidateNull(_ string) error {
return nil return nil
} }

View file

@ -223,7 +223,6 @@ func Test_checkAnnotation(t *testing.T) {
} }
func TestCheckAnnotationRisk(t *testing.T) { func TestCheckAnnotationRisk(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
annotations map[string]string annotations map[string]string

View file

@ -45,9 +45,7 @@ const (
proxyMaxTempFileSizeAnnotation = "proxy-max-temp-file-size" proxyMaxTempFileSizeAnnotation = "proxy-max-temp-file-size"
) )
var ( var validUpstreamAnnotation = regexp.MustCompile(`^((error|timeout|invalid_header|http_500|http_502|http_503|http_504|http_403|http_404|http_429|non_idempotent|off)\s?)+$`)
validUpstreamAnnotation = regexp.MustCompile(`^((error|timeout|invalid_header|http_500|http_502|http_503|http_504|http_403|http_404|http_429|non_idempotent|off)\s?)+$`)
)
var proxyAnnotations = parser.Annotation{ var proxyAnnotations = parser.Annotation{
Group: "backend", Group: "backend",
@ -78,32 +76,32 @@ var proxyAnnotations = parser.Annotation{
By default proxy buffers number is set as 4`, By default proxy buffers number is set as 4`,
}, },
proxyBufferSizeAnnotation: { proxyBufferSizeAnnotation: {
Validator: parser.ValidateRegex(*parser.SizeRegex, true), Validator: parser.ValidateRegex(parser.SizeRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation sets the size of the buffer proxy_buffer_size used for reading the first part of the response received from the proxied server. Documentation: `This annotation sets the size of the buffer proxy_buffer_size used for reading the first part of the response received from the proxied server.
By default proxy buffer size is set as "4k".`, By default proxy buffer size is set as "4k".`,
}, },
proxyCookiePathAnnotation: { proxyCookiePathAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true), Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation sets a text that should be changed in the path attribute of the "Set-Cookie" header fields of a proxied server response.`, Documentation: `This annotation sets a text that should be changed in the path attribute of the "Set-Cookie" header fields of a proxied server response.`,
}, },
proxyCookieDomainAnnotation: { proxyCookieDomainAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation ets a text that should be changed in the domain attribute of the "Set-Cookie" header fields of a proxied server response.`, Documentation: `This annotation ets a text that should be changed in the domain attribute of the "Set-Cookie" header fields of a proxied server response.`,
}, },
proxyBodySizeAnnotation: { proxyBodySizeAnnotation: {
Validator: parser.ValidateRegex(*parser.SizeRegex, true), Validator: parser.ValidateRegex(parser.SizeRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation allows setting the maximum allowed size of a client request body.`, Documentation: `This annotation allows setting the maximum allowed size of a client request body.`,
}, },
proxyNextUpstreamAnnotation: { proxyNextUpstreamAnnotation: {
Validator: parser.ValidateRegex(*validUpstreamAnnotation, false), Validator: parser.ValidateRegex(validUpstreamAnnotation, false),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines when the next upstream should be used. Documentation: `This annotation defines when the next upstream should be used.
@ -129,13 +127,13 @@ var proxyAnnotations = parser.Annotation{
Documentation: `This annotation enables or disables buffering of a client request body.`, Documentation: `This annotation enables or disables buffering of a client request body.`,
}, },
proxyRedirectFromAnnotation: { proxyRedirectFromAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true), Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `The annotations proxy-redirect-from and proxy-redirect-to will set the first and second parameters of NGINX's proxy_redirect directive respectively`, Documentation: `The annotations proxy-redirect-from and proxy-redirect-to will set the first and second parameters of NGINX's proxy_redirect directive respectively`,
}, },
proxyRedirectToAnnotation: { proxyRedirectToAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true), Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `The annotations proxy-redirect-from and proxy-redirect-to will set the first and second parameters of NGINX's proxy_redirect directive respectively`, Documentation: `The annotations proxy-redirect-from and proxy-redirect-to will set the first and second parameters of NGINX's proxy_redirect directive respectively`,
@ -153,7 +151,7 @@ var proxyAnnotations = parser.Annotation{
Documentation: `This annotations sets the HTTP protocol version for proxying. Can be "1.0" or "1.1".`, Documentation: `This annotations sets the HTTP protocol version for proxying. Can be "1.0" or "1.1".`,
}, },
proxyMaxTempFileSizeAnnotation: { proxyMaxTempFileSizeAnnotation: {
Validator: parser.ValidateRegex(*parser.SizeRegex, true), Validator: parser.ValidateRegex(parser.SizeRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation defines the maximum size of a temporary file when buffering responses.`, Documentation: `This annotation defines the maximum size of a temporary file when buffering responses.`,
@ -253,7 +251,8 @@ type proxy struct {
// NewParser creates a new reverse proxy configuration annotation parser // NewParser creates a new reverse proxy configuration annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation { func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return proxy{r: r, return proxy{
r: r,
annotationConfig: proxyAnnotations, annotationConfig: proxyAnnotations,
} }
} }

View file

@ -28,6 +28,12 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
const (
off = "off"
proxyHTTPVersion = "1.0"
proxyMaxTempFileSize = "128k"
)
func buildIngress() *networking.Ingress { func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{ defaultBackend := networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
@ -87,7 +93,7 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
ProxyNextUpstreamTimeout: 0, ProxyNextUpstreamTimeout: 0,
ProxyNextUpstreamTries: 3, ProxyNextUpstreamTries: 3,
ProxyRequestBuffering: "on", ProxyRequestBuffering: "on",
ProxyBuffering: "off", ProxyBuffering: off,
ProxyHTTPVersion: "1.1", ProxyHTTPVersion: "1.1",
ProxyMaxTempFileSize: "1024m", ProxyMaxTempFileSize: "1024m",
} }
@ -103,13 +109,13 @@ func TestProxy(t *testing.T) {
data[parser.GetAnnotationWithPrefix("proxy-buffers-number")] = "8" data[parser.GetAnnotationWithPrefix("proxy-buffers-number")] = "8"
data[parser.GetAnnotationWithPrefix("proxy-buffer-size")] = "1k" data[parser.GetAnnotationWithPrefix("proxy-buffer-size")] = "1k"
data[parser.GetAnnotationWithPrefix("proxy-body-size")] = "2k" data[parser.GetAnnotationWithPrefix("proxy-body-size")] = "2k"
data[parser.GetAnnotationWithPrefix("proxy-next-upstream")] = "off" data[parser.GetAnnotationWithPrefix("proxy-next-upstream")] = off
data[parser.GetAnnotationWithPrefix("proxy-next-upstream-timeout")] = "5" data[parser.GetAnnotationWithPrefix("proxy-next-upstream-timeout")] = "5"
data[parser.GetAnnotationWithPrefix("proxy-next-upstream-tries")] = "3" data[parser.GetAnnotationWithPrefix("proxy-next-upstream-tries")] = "3"
data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = "off" data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = off
data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on" data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on"
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = "1.0" data[parser.GetAnnotationWithPrefix("proxy-http-version")] = proxyHTTPVersion
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = "128k" data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = proxyMaxTempFileSize
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, err := NewParser(mockBackend{}).Parse(ing) i, err := NewParser(mockBackend{}).Parse(ing)
@ -138,7 +144,7 @@ func TestProxy(t *testing.T) {
if p.BodySize != "2k" { if p.BodySize != "2k" {
t.Errorf("expected 2k as body-size but returned %v", p.BodySize) t.Errorf("expected 2k as body-size but returned %v", p.BodySize)
} }
if p.NextUpstream != "off" { if p.NextUpstream != off {
t.Errorf("expected off as next-upstream but returned %v", p.NextUpstream) t.Errorf("expected off as next-upstream but returned %v", p.NextUpstream)
} }
if p.NextUpstreamTimeout != 5 { if p.NextUpstreamTimeout != 5 {
@ -147,16 +153,16 @@ func TestProxy(t *testing.T) {
if p.NextUpstreamTries != 3 { if p.NextUpstreamTries != 3 {
t.Errorf("expected 3 as next-upstream-tries but returned %v", p.NextUpstreamTries) t.Errorf("expected 3 as next-upstream-tries but returned %v", p.NextUpstreamTries)
} }
if p.RequestBuffering != "off" { if p.RequestBuffering != off {
t.Errorf("expected off as request-buffering but returned %v", p.RequestBuffering) t.Errorf("expected off as request-buffering but returned %v", p.RequestBuffering)
} }
if p.ProxyBuffering != "on" { if p.ProxyBuffering != "on" {
t.Errorf("expected on as proxy-buffering but returned %v", p.ProxyBuffering) t.Errorf("expected on as proxy-buffering but returned %v", p.ProxyBuffering)
} }
if p.ProxyHTTPVersion != "1.0" { if p.ProxyHTTPVersion != proxyHTTPVersion {
t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion) t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
} }
if p.ProxyMaxTempFileSize != "128k" { if p.ProxyMaxTempFileSize != proxyMaxTempFileSize {
t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize) t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
} }
} }
@ -176,8 +182,8 @@ func TestProxyComplex(t *testing.T) {
data[parser.GetAnnotationWithPrefix("proxy-next-upstream-tries")] = "3" data[parser.GetAnnotationWithPrefix("proxy-next-upstream-tries")] = "3"
data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = "off" data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = "off"
data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on" data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on"
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = "1.0" data[parser.GetAnnotationWithPrefix("proxy-http-version")] = proxyHTTPVersion
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = "128k" data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = proxyMaxTempFileSize
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, err := NewParser(mockBackend{}).Parse(ing) i, err := NewParser(mockBackend{}).Parse(ing)
@ -221,10 +227,10 @@ func TestProxyComplex(t *testing.T) {
if p.ProxyBuffering != "on" { if p.ProxyBuffering != "on" {
t.Errorf("expected on as proxy-buffering but returned %v", p.ProxyBuffering) t.Errorf("expected on as proxy-buffering but returned %v", p.ProxyBuffering)
} }
if p.ProxyHTTPVersion != "1.0" { if p.ProxyHTTPVersion != proxyHTTPVersion {
t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion) t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
} }
if p.ProxyMaxTempFileSize != "128k" { if p.ProxyMaxTempFileSize != proxyMaxTempFileSize {
t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize) t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
} }
} }

View file

@ -24,7 +24,6 @@ import (
networking "k8s.io/api/networking/v1" networking "k8s.io/api/networking/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors" ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
"k8s.io/ingress-nginx/internal/k8s" "k8s.io/ingress-nginx/internal/k8s"
@ -42,7 +41,7 @@ const (
var ( var (
proxySSLOnOffRegex = regexp.MustCompile(`^(on|off)$`) proxySSLOnOffRegex = regexp.MustCompile(`^(on|off)$`)
proxySSLProtocolRegex = regexp.MustCompile(`^(SSLv2|SSLv3|TLSv1|TLSv1\.1|TLSv1\.2|TLSv1\.3| )*$`) proxySSLProtocolRegex = regexp.MustCompile(`^(SSLv2|SSLv3|TLSv1|TLSv1\.1|TLSv1\.2|TLSv1\.3| )*$`)
proxySSLCiphersRegex = regexp.MustCompile(`^[A-Za-z0-9\+\:\_\-\!]*$`) proxySSLCiphersRegex = regexp.MustCompile(`^[A-Za-z0-9\+:\_\-!]*$`)
) )
const ( const (
@ -59,7 +58,7 @@ var proxySSLAnnotation = parser.Annotation{
Group: "proxy", Group: "proxy",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
proxySSLSecretAnnotation: { proxySSLSecretAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation specifies a Secret with the certificate tls.crt, key tls.key in PEM format used for authentication to a proxied HTTPS server. Documentation: `This annotation specifies a Secret with the certificate tls.crt, key tls.key in PEM format used for authentication to a proxied HTTPS server.
@ -68,14 +67,14 @@ var proxySSLAnnotation = parser.Annotation{
Just secrets on the same namespace of the ingress can be used.`, Just secrets on the same namespace of the ingress can be used.`,
}, },
proxySSLCiphersAnnotation: { proxySSLCiphersAnnotation: {
Validator: parser.ValidateRegex(*proxySSLCiphersRegex, true), Validator: parser.ValidateRegex(proxySSLCiphersRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation Specifies the enabled ciphers for requests to a proxied HTTPS server. Documentation: `This annotation Specifies the enabled ciphers for requests to a proxied HTTPS server.
The ciphers are specified in the format understood by the OpenSSL library.`, The ciphers are specified in the format understood by the OpenSSL library.`,
}, },
proxySSLProtocolsAnnotation: { proxySSLProtocolsAnnotation: {
Validator: parser.ValidateRegex(*proxySSLProtocolRegex, true), Validator: parser.ValidateRegex(proxySSLProtocolRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation enables the specified protocols for requests to a proxied HTTPS server.`, Documentation: `This annotation enables the specified protocols for requests to a proxied HTTPS server.`,
@ -88,7 +87,7 @@ var proxySSLAnnotation = parser.Annotation{
This value is also passed through SNI when a connection is established to the proxied HTTPS server.`, This value is also passed through SNI when a connection is established to the proxied HTTPS server.`,
}, },
proxySSLVerifyAnnotation: { proxySSLVerifyAnnotation: {
Validator: parser.ValidateRegex(*proxySSLOnOffRegex, true), Validator: parser.ValidateRegex(proxySSLOnOffRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation enables or disables verification of the proxied HTTPS server certificate. (default: off)`, Documentation: `This annotation enables or disables verification of the proxied HTTPS server certificate. (default: off)`,
@ -100,7 +99,7 @@ var proxySSLAnnotation = parser.Annotation{
Documentation: `This annotation Sets the verification depth in the proxied HTTPS server certificates chain. (default: 1).`, Documentation: `This annotation Sets the verification depth in the proxied HTTPS server certificates chain. (default: 1).`,
}, },
proxySSLServerNameAnnotation: { proxySSLServerNameAnnotation: {
Validator: parser.ValidateRegex(*proxySSLOnOffRegex, true), Validator: parser.ValidateRegex(proxySSLOnOffRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation enables passing of the server name through TLS Server Name Indication extension (SNI, RFC 6066) when establishing a connection with the proxied HTTPS server.`, Documentation: `This annotation enables passing of the server name through TLS Server Name Indication extension (SNI, RFC 6066) when establishing a connection with the proxied HTTPS server.`,
@ -150,10 +149,11 @@ func (pssl1 *Config) Equal(pssl2 *Config) bool {
} }
// NewParser creates a new TLS authentication annotation parser // NewParser creates a new TLS authentication annotation parser
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation { func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return proxySSL{ return proxySSL{
r: resolver, r: r,
annotationConfig: proxySSLAnnotation} annotationConfig: proxySSLAnnotation,
}
} }
type proxySSL struct { type proxySSL struct {
@ -208,13 +208,13 @@ func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) {
proxyCert, err := p.r.GetAuthCertificate(proxysslsecret) proxyCert, err := p.r.GetAuthCertificate(proxysslsecret)
if err != nil { if err != nil {
e := fmt.Errorf("error obtaining certificate: %w", err) e := fmt.Errorf("error obtaining certificate: %w", err)
return &Config{}, ing_errors.LocationDenied{Reason: e} return &Config{}, ing_errors.LocationDeniedError{Reason: e}
} }
config.AuthSSLCert = *proxyCert config.AuthSSLCert = *proxyCert
config.Ciphers, err = parser.GetStringAnnotation(proxySSLCiphersAnnotation, ing, p.annotationConfig.Annotations) config.Ciphers, err = parser.GetStringAnnotation(proxySSLCiphersAnnotation, ing, p.annotationConfig.Annotations)
if err != nil { if err != nil {
if errors.IsValidationError(err) { if ing_errors.IsValidationError(err) {
klog.Warningf("invalid value passed to proxy-ssl-ciphers, defaulting to %s", defaultProxySSLCiphers) klog.Warningf("invalid value passed to proxy-ssl-ciphers, defaulting to %s", defaultProxySSLCiphers)
} }
config.Ciphers = defaultProxySSLCiphers config.Ciphers = defaultProxySSLCiphers
@ -222,7 +222,7 @@ func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) {
config.Protocols, err = parser.GetStringAnnotation(proxySSLProtocolsAnnotation, ing, p.annotationConfig.Annotations) config.Protocols, err = parser.GetStringAnnotation(proxySSLProtocolsAnnotation, ing, p.annotationConfig.Annotations)
if err != nil { if err != nil {
if errors.IsValidationError(err) { if ing_errors.IsValidationError(err) {
klog.Warningf("invalid value passed to proxy-ssl-protocols, defaulting to %s", defaultProxySSLProtocols) klog.Warningf("invalid value passed to proxy-ssl-protocols, defaulting to %s", defaultProxySSLProtocols)
} }
config.Protocols = defaultProxySSLProtocols config.Protocols = defaultProxySSLProtocols
@ -232,7 +232,7 @@ func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) {
config.ProxySSLName, err = parser.GetStringAnnotation(proxySSLNameAnnotation, ing, p.annotationConfig.Annotations) config.ProxySSLName, err = parser.GetStringAnnotation(proxySSLNameAnnotation, ing, p.annotationConfig.Annotations)
if err != nil { if err != nil {
if errors.IsValidationError(err) { if ing_errors.IsValidationError(err) {
klog.Warningf("invalid value passed to proxy-ssl-name, defaulting to empty") klog.Warningf("invalid value passed to proxy-ssl-name, defaulting to empty")
} }
config.ProxySSLName = "" config.ProxySSLName = ""
@ -260,7 +260,7 @@ func (p proxySSL) GetDocumentation() parser.AnnotationFields {
return p.annotationConfig.Annotations return p.annotationConfig.Annotations
} }
func (a proxySSL) Validate(anns map[string]string) error { func (p proxySSL) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(p.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, proxySSLAnnotation.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, proxySSLAnnotation.Annotations)
} }

View file

@ -27,6 +27,14 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver" "k8s.io/ingress-nginx/internal/ingress/resolver"
) )
const (
defaultDemoSecret = "default/demo-secret"
proxySslCiphers = "HIGH:-SHA"
off = "off"
sslServerName = "w00t"
defaultProtocol = "SSLv2 TLSv1 TLSv1.2 TLSv1.3"
)
func buildIngress() *networking.Ingress { func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{ defaultBackend := networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
@ -77,28 +85,27 @@ type mockSecret struct {
// GetAuthCertificate from mockSecret mocks the GetAuthCertificate for backend certificate authentication // GetAuthCertificate from mockSecret mocks the GetAuthCertificate for backend certificate authentication
func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) { func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
if name != "default/demo-secret" { if name != defaultDemoSecret {
return nil, errors.Errorf("there is no secret with name %v", name) return nil, errors.Errorf("there is no secret with name %v", name)
} }
return &resolver.AuthSSLCert{ return &resolver.AuthSSLCert{
Secret: "default/demo-secret", Secret: defaultDemoSecret,
CAFileName: "/ssl/ca.crt", CAFileName: "/ssl/ca.crt",
CASHA: "abc", CASHA: "abc",
}, nil }, nil
} }
func TestAnnotations(t *testing.T) { func TestAnnotations(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[parser.GetAnnotationWithPrefix(proxySSLSecretAnnotation)] = "default/demo-secret" data[parser.GetAnnotationWithPrefix(proxySSLSecretAnnotation)] = defaultDemoSecret
data[parser.GetAnnotationWithPrefix("proxy-ssl-ciphers")] = "HIGH:-SHA" data[parser.GetAnnotationWithPrefix("proxy-ssl-ciphers")] = proxySslCiphers
data[parser.GetAnnotationWithPrefix("proxy-ssl-name")] = "$host" data[parser.GetAnnotationWithPrefix("proxy-ssl-name")] = "$host"
data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv1.3 SSLv2 TLSv1 TLSv1.2" data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv1.3 SSLv2 TLSv1 TLSv1.2"
data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = "on" data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = "on"
data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = "off" data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = off
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = "on" data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = "on"
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "3" data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "3"
@ -115,7 +122,7 @@ func TestAnnotations(t *testing.T) {
t.Errorf("expected *Config but got %v", u) t.Errorf("expected *Config but got %v", u)
} }
secret, err := fakeSecret.GetAuthCertificate("default/demo-secret") secret, err := fakeSecret.GetAuthCertificate(defaultDemoSecret)
if err != nil { if err != nil {
t.Errorf("unexpected error getting secret %v", err) t.Errorf("unexpected error getting secret %v", err)
} }
@ -123,11 +130,11 @@ func TestAnnotations(t *testing.T) {
if u.AuthSSLCert.Secret != secret.Secret { if u.AuthSSLCert.Secret != secret.Secret {
t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret) t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret)
} }
if u.Ciphers != "HIGH:-SHA" { if u.Ciphers != proxySslCiphers {
t.Errorf("expected %v but got %v", "HIGH:-SHA", u.Ciphers) t.Errorf("expected %v but got %v", proxySslCiphers, u.Ciphers)
} }
if u.Protocols != "SSLv2 TLSv1 TLSv1.2 TLSv1.3" { if u.Protocols != defaultProtocol {
t.Errorf("expected %v but got %v", "SSLv2 TLSv1 TLSv1.2 TLSv1.3", u.Protocols) t.Errorf("expected %v but got %v", defaultProtocol, u.Protocols)
} }
if u.Verify != "on" { if u.Verify != "on" {
t.Errorf("expected %v but got %v", "on", u.Verify) t.Errorf("expected %v but got %v", "on", u.Verify)
@ -141,7 +148,6 @@ func TestAnnotations(t *testing.T) {
if u.ProxySSLServerName != "on" { if u.ProxySSLServerName != "on" {
t.Errorf("expected %v but got %v", "on", u.ProxySSLServerName) t.Errorf("expected %v but got %v", "on", u.ProxySSLServerName)
} }
} }
func TestInvalidAnnotations(t *testing.T) { func TestInvalidAnnotations(t *testing.T) {
@ -172,11 +178,11 @@ func TestInvalidAnnotations(t *testing.T) {
} }
// Invalid optional Annotations // Invalid optional Annotations
data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "default/demo-secret" data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = defaultDemoSecret
data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv111 SSLv1" data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv111 SSLv1"
data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = "w00t" data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = sslServerName
data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = "w00t" data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = sslServerName
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = "w00t" data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = sslServerName
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "abcd" data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "abcd"
ing.SetAnnotations(data) ing.SetAnnotations(data)
@ -207,21 +213,15 @@ func TestEquals(t *testing.T) {
cfg1 := &Config{} cfg1 := &Config{}
cfg2 := &Config{} cfg2 := &Config{}
// Same config
result := cfg1.Equal(cfg1)
if result != true {
t.Errorf("Expected true")
}
// compare nil // compare nil
result = cfg1.Equal(nil) result := cfg1.Equal(nil)
if result != false { if result != false {
t.Errorf("Expected false") t.Errorf("Expected false")
} }
// Different Certs // Different Certs
sslCert1 := resolver.AuthSSLCert{ sslCert1 := resolver.AuthSSLCert{
Secret: "default/demo-secret", Secret: defaultDemoSecret,
CAFileName: "/ssl/ca.crt", CAFileName: "/ssl/ca.crt",
CASHA: "abc", CASHA: "abc",
} }
@ -240,7 +240,7 @@ func TestEquals(t *testing.T) {
// Different Ciphers // Different Ciphers
cfg1.Ciphers = "DEFAULT" cfg1.Ciphers = "DEFAULT"
cfg2.Ciphers = "HIGH:-SHA" cfg2.Ciphers = proxySslCiphers
result = cfg1.Equal(cfg2) result = cfg1.Equal(cfg2)
if result != false { if result != false {
t.Errorf("Expected false") t.Errorf("Expected false")
@ -248,22 +248,22 @@ func TestEquals(t *testing.T) {
cfg2.Ciphers = "DEFAULT" cfg2.Ciphers = "DEFAULT"
// Different Protocols // Different Protocols
cfg1.Protocols = "SSLv2 TLSv1 TLSv1.2 TLSv1.3" cfg1.Protocols = defaultProtocol
cfg2.Protocols = "SSLv3 TLSv1 TLSv1.2 TLSv1.3" cfg2.Protocols = "SSLv3 TLSv1 TLSv1.2 TLSv1.3"
result = cfg1.Equal(cfg2) result = cfg1.Equal(cfg2)
if result != false { if result != false {
t.Errorf("Expected false") t.Errorf("Expected false")
} }
cfg2.Protocols = "SSLv2 TLSv1 TLSv1.2 TLSv1.3" cfg2.Protocols = defaultProtocol
// Different Verify // Different Verify
cfg1.Verify = "off" cfg1.Verify = off
cfg2.Verify = "on" cfg2.Verify = "on"
result = cfg1.Equal(cfg2) result = cfg1.Equal(cfg2)
if result != false { if result != false {
t.Errorf("Expected false") t.Errorf("Expected false")
} }
cfg2.Verify = "off" cfg2.Verify = off
// Different VerifyDepth // Different VerifyDepth
cfg1.VerifyDepth = 1 cfg1.VerifyDepth = 1
@ -275,13 +275,13 @@ func TestEquals(t *testing.T) {
cfg2.VerifyDepth = 1 cfg2.VerifyDepth = 1
// Different ProxySSLServerName // Different ProxySSLServerName
cfg1.ProxySSLServerName = "off" cfg1.ProxySSLServerName = off
cfg2.ProxySSLServerName = "on" cfg2.ProxySSLServerName = "on"
result = cfg1.Equal(cfg2) result = cfg1.Equal(cfg2)
if result != false { if result != false {
t.Errorf("Expected false") t.Errorf("Expected false")
} }
cfg2.ProxySSLServerName = "off" cfg2.ProxySSLServerName = off
// Equal Configs // Equal Configs
result = cfg1.Equal(cfg2) result = cfg1.Equal(cfg2)

View file

@ -288,7 +288,7 @@ func (a ratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
func encode(s string) string { func encode(s string) string {
str := base64.URLEncoding.EncodeToString([]byte(s)) str := base64.URLEncoding.EncodeToString([]byte(s))
return strings.Replace(str, "=", "", -1) return strings.ReplaceAll(str, "=", "")
} }
func (a ratelimit) GetDocumentation() parser.AnnotationFields { func (a ratelimit) GetDocumentation() parser.AnnotationFields {

View file

@ -54,14 +54,14 @@ var redirectAnnotations = parser.Annotation{
Documentation: `In some scenarios is required to redirect from www.domain.com to domain.com or vice versa. To enable this feature use this annotation.`, Documentation: `In some scenarios is required to redirect from www.domain.com to domain.com or vice versa. To enable this feature use this annotation.`,
}, },
temporalRedirectAnnotation: { temporalRedirectAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, false), Validator: parser.ValidateRegex(parser.URLIsValidRegex, false),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, // Medium, as it allows arbitrary URLs that needs to be validated Risk: parser.AnnotationRiskMedium, // Medium, as it allows arbitrary URLs that needs to be validated
Documentation: `This annotation allows you to return a temporal redirect (Return Code 302) instead of sending data to the upstream. Documentation: `This annotation allows you to return a temporal redirect (Return Code 302) instead of sending data to the upstream.
For example setting this annotation to https://www.google.com would redirect everything to Google with a Return Code of 302 (Moved Temporarily).`, For example setting this annotation to https://www.google.com would redirect everything to Google with a Return Code of 302 (Moved Temporarily).`,
}, },
permanentRedirectAnnotation: { permanentRedirectAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, false), Validator: parser.ValidateRegex(parser.URLIsValidRegex, false),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, // Medium, as it allows arbitrary URLs that needs to be validated Risk: parser.AnnotationRiskMedium, // Medium, as it allows arbitrary URLs that needs to be validated
Documentation: `This annotation allows to return a permanent redirect (Return Code 301) instead of sending data to the upstream. Documentation: `This annotation allows to return a permanent redirect (Return Code 301) instead of sending data to the upstream.
@ -174,11 +174,11 @@ func isValidURL(s string) error {
return nil return nil
} }
func (a redirect) GetDocumentation() parser.AnnotationFields { func (r redirect) GetDocumentation() parser.AnnotationFields {
return a.annotationConfig.Annotations return r.annotationConfig.Annotations
} }
func (a redirect) Validate(anns map[string]string) error { func (r redirect) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(r.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, redirectAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, redirectAnnotations.Annotations)
} }

View file

@ -136,7 +136,6 @@ func TestTemporalRedirect(t *testing.T) {
} }
func TestIsValidURL(t *testing.T) { func TestIsValidURL(t *testing.T) {
invalid := "ok.com" invalid := "ok.com"
urlParse, err := url.Parse(invalid) urlParse, err := url.Parse(invalid)
if err != nil { if err != nil {

View file

@ -40,7 +40,7 @@ var rewriteAnnotations = parser.Annotation{
Group: "rewrite", Group: "rewrite",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
rewriteTargetAnnotation: { rewriteTargetAnnotation: {
Validator: parser.ValidateRegex(*parser.RegexPathWithCapture, false), Validator: parser.ValidateRegex(parser.RegexPathWithCapture, false),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation allows to specify the target URI where the traffic must be redirected. It can contain regular characters and captured Documentation: `This annotation allows to specify the target URI where the traffic must be redirected. It can contain regular characters and captured
@ -72,7 +72,7 @@ var rewriteAnnotations = parser.Annotation{
the pathType should also be defined as 'ImplementationSpecific'.`, the pathType should also be defined as 'ImplementationSpecific'.`,
}, },
appRootAnnotation: { appRootAnnotation: {
Validator: parser.ValidateRegex(*parser.RegexPathWithCapture, false), Validator: parser.ValidateRegex(parser.RegexPathWithCapture, false),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines the Application Root that the Controller must redirect if it's in / context`, Documentation: `This annotation defines the Application Root that the Controller must redirect if it's in / context`,

View file

@ -120,7 +120,10 @@ func TestSSLRedirect(t *testing.T) {
data[parser.GetAnnotationWithPrefix("rewrite-target")] = defRoute data[parser.GetAnnotationWithPrefix("rewrite-target")] = defRoute
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{redirect: true}).Parse(ing) i, err := NewParser(mockBackend{redirect: true}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
redirect, ok := i.(*Config) redirect, ok := i.(*Config)
if !ok { if !ok {
t.Errorf("expected a Redirect type") t.Errorf("expected a Redirect type")
@ -132,7 +135,10 @@ func TestSSLRedirect(t *testing.T) {
data[parser.GetAnnotationWithPrefix("rewrite-target")] = "/xpto/$1/abc/$2" data[parser.GetAnnotationWithPrefix("rewrite-target")] = "/xpto/$1/abc/$2"
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, _ = NewParser(mockBackend{redirect: true}).Parse(ing) i, err = NewParser(mockBackend{redirect: true}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
redirect, ok = i.(*Config) redirect, ok = i.(*Config)
if !ok { if !ok {
t.Errorf("expected a Redirect type") t.Errorf("expected a Redirect type")
@ -144,7 +150,10 @@ func TestSSLRedirect(t *testing.T) {
data[parser.GetAnnotationWithPrefix("rewrite-target")] = "/xpto/xas{445}" data[parser.GetAnnotationWithPrefix("rewrite-target")] = "/xpto/xas{445}"
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, _ = NewParser(mockBackend{redirect: true}).Parse(ing) i, err = NewParser(mockBackend{redirect: true}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
redirect, ok = i.(*Config) redirect, ok = i.(*Config)
if !ok { if !ok {
t.Errorf("expected a Redirect type") t.Errorf("expected a Redirect type")
@ -156,7 +165,10 @@ func TestSSLRedirect(t *testing.T) {
data[parser.GetAnnotationWithPrefix("ssl-redirect")] = "false" data[parser.GetAnnotationWithPrefix("ssl-redirect")] = "false"
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, _ = NewParser(mockBackend{redirect: false}).Parse(ing) i, err = NewParser(mockBackend{redirect: false}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
redirect, ok = i.(*Config) redirect, ok = i.(*Config)
if !ok { if !ok {
t.Errorf("expected a Redirect type") t.Errorf("expected a Redirect type")
@ -173,7 +185,10 @@ func TestForceSSLRedirect(t *testing.T) {
data[parser.GetAnnotationWithPrefix("rewrite-target")] = defRoute data[parser.GetAnnotationWithPrefix("rewrite-target")] = defRoute
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{redirect: true}).Parse(ing) i, err := NewParser(mockBackend{redirect: true}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
redirect, ok := i.(*Config) redirect, ok := i.(*Config)
if !ok { if !ok {
t.Errorf("expected a Redirect type") t.Errorf("expected a Redirect type")
@ -185,7 +200,10 @@ func TestForceSSLRedirect(t *testing.T) {
data[parser.GetAnnotationWithPrefix("force-ssl-redirect")] = "true" data[parser.GetAnnotationWithPrefix("force-ssl-redirect")] = "true"
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, _ = NewParser(mockBackend{redirect: false}).Parse(ing) i, err = NewParser(mockBackend{redirect: false}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
redirect, ok = i.(*Config) redirect, ok = i.(*Config)
if !ok { if !ok {
t.Errorf("expected a Redirect type") t.Errorf("expected a Redirect type")
@ -194,6 +212,7 @@ func TestForceSSLRedirect(t *testing.T) {
t.Errorf("Expected true but returned false") t.Errorf("Expected true but returned false")
} }
} }
func TestAppRoot(t *testing.T) { func TestAppRoot(t *testing.T) {
ap := NewParser(mockBackend{redirect: true}) ap := NewParser(mockBackend{redirect: true})
@ -241,7 +260,10 @@ func TestUseRegex(t *testing.T) {
data[parser.GetAnnotationWithPrefix("use-regex")] = "true" data[parser.GetAnnotationWithPrefix("use-regex")] = "true"
ing.SetAnnotations(data) ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{redirect: true}).Parse(ing) i, err := NewParser(mockBackend{redirect: true}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
redirect, ok := i.(*Config) redirect, ok := i.(*Config)
if !ok { if !ok {
t.Errorf("expected a App Context") t.Errorf("expected a App Context")

View file

@ -69,7 +69,7 @@ func (s satisfy) GetDocumentation() parser.AnnotationFields {
return s.annotationConfig.Annotations return s.annotationConfig.Annotations
} }
func (a satisfy) Validate(anns map[string]string) error { func (s satisfy) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(s.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, satisfyAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, satisfyAnnotations.Annotations)
} }

View file

@ -54,6 +54,7 @@ func TestParse(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations) ing.SetAnnotations(testCase.annotations)
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
result, _ := ap.Parse(ing) result, _ := ap.Parse(ing)
if result != testCase.expected { if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)

View file

@ -69,7 +69,7 @@ func (s serviceUpstream) GetDocumentation() parser.AnnotationFields {
return s.annotationConfig.Annotations return s.annotationConfig.Annotations
} }
func (a serviceUpstream) Validate(anns map[string]string) error { func (s serviceUpstream) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(s.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, serviceUpstreamAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, serviceUpstreamAnnotations.Annotations)
} }

View file

@ -77,7 +77,10 @@ func TestIngressAnnotationServiceUpstreamEnabled(t *testing.T) {
data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "true" data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "true"
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing) val, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
enabled, ok := val.(bool) enabled, ok := val.(bool)
if !ok { if !ok {
t.Errorf("expected a bool type") t.Errorf("expected a bool type")
@ -96,7 +99,10 @@ func TestIngressAnnotationServiceUpstreamSetFalse(t *testing.T) {
data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "false" data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "false"
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing) val, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
enabled, ok := val.(bool) enabled, ok := val.(bool)
if !ok { if !ok {
t.Errorf("expected a bool type") t.Errorf("expected a bool type")
@ -110,7 +116,10 @@ func TestIngressAnnotationServiceUpstreamSetFalse(t *testing.T) {
data = map[string]string{} data = map[string]string{}
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ = NewParser(&resolver.Mock{}).Parse(ing) val, err = NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
enabled, ok = val.(bool) enabled, ok = val.(bool)
if !ok { if !ok {
t.Errorf("expected a bool type") t.Errorf("expected a bool type")
@ -137,7 +146,10 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
func TestParseAnnotationsWithDefaultConfig(t *testing.T) { func TestParseAnnotationsWithDefaultConfig(t *testing.T) {
ing := buildIngress() ing := buildIngress()
val, _ := NewParser(mockBackend{}).Parse(ing) val, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
enabled, ok := val.(bool) enabled, ok := val.(bool)
if !ok { if !ok {
@ -158,7 +170,10 @@ func TestParseAnnotationsOverridesDefaultConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "false" data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "false"
ing.SetAnnotations(data) ing.SetAnnotations(data)
val, _ := NewParser(mockBackend{}).Parse(ing) val, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
enabled, ok := val.(bool) enabled, ok := val.(bool)
if !ok { if !ok {

View file

@ -63,13 +63,15 @@ const (
// This is used to control the cookie change after request failure // This is used to control the cookie change after request failure
annotationAffinityCookieChangeOnFailure = "session-cookie-change-on-failure" annotationAffinityCookieChangeOnFailure = "session-cookie-change-on-failure"
cookieAffinity = "cookie"
) )
var sessionAffinityAnnotations = parser.Annotation{ var sessionAffinityAnnotations = parser.Annotation{
Group: "affinity", Group: "affinity",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
annotationAffinityType: { annotationAffinityType: {
Validator: parser.ValidateOptions([]string{"cookie"}, true, true), Validator: parser.ValidateOptions([]string{cookieAffinity}, true, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `This annotation enables and sets the affinity type in all Upstreams of an Ingress. This way, a request will always be directed to the same upstream server. The only affinity type available for NGINX is cookie`, Documentation: `This annotation enables and sets the affinity type in all Upstreams of an Ingress. This way, a request will always be directed to the same upstream server. The only affinity type available for NGINX is cookie`,
@ -91,7 +93,7 @@ var sessionAffinityAnnotations = parser.Annotation{
Setting this to legacy will restore original canary behavior, when session affinity was ignored.`, Setting this to legacy will restore original canary behavior, when session affinity was ignored.`,
}, },
annotationAffinityCookieName: { annotationAffinityCookieName: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation allows to specify the name of the cookie that will be used to route the requests`, Documentation: `This annotation allows to specify the name of the cookie that will be used to route the requests`,
@ -103,25 +105,25 @@ var sessionAffinityAnnotations = parser.Annotation{
Documentation: `This annotation set the cookie as secure regardless the protocol of the incoming request`, Documentation: `This annotation set the cookie as secure regardless the protocol of the incoming request`,
}, },
annotationAffinityCookieExpires: { annotationAffinityCookieExpires: {
Validator: parser.ValidateRegex(*affinityCookieExpiresRegex, true), Validator: parser.ValidateRegex(affinityCookieExpiresRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation is a legacy version of "session-cookie-max-age" for compatibility with older browsers, generates an "Expires" cookie directive by adding the seconds to the current date`, Documentation: `This annotation is a legacy version of "session-cookie-max-age" for compatibility with older browsers, generates an "Expires" cookie directive by adding the seconds to the current date`,
}, },
annotationAffinityCookieMaxAge: { annotationAffinityCookieMaxAge: {
Validator: parser.ValidateRegex(*affinityCookieExpiresRegex, false), Validator: parser.ValidateRegex(affinityCookieExpiresRegex, false),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation sets the time until the cookie expires`, Documentation: `This annotation sets the time until the cookie expires`,
}, },
annotationAffinityCookiePath: { annotationAffinityCookiePath: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true), Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines the Path that will be set on the cookie (required if your Ingress paths use regular expressions)`, Documentation: `This annotation defines the Path that will be set on the cookie (required if your Ingress paths use regular expressions)`,
}, },
annotationAffinityCookieDomain: { annotationAffinityCookieDomain: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium, Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines the Domain attribute of the sticky cookie.`, Documentation: `This annotation defines the Domain attribute of the sticky cookie.`,
@ -149,9 +151,7 @@ var sessionAffinityAnnotations = parser.Annotation{
}, },
} }
var ( var affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
)
// Config describes the per ingress session affinity config // Config describes the per ingress session affinity config
type Config struct { type Config struct {
@ -186,6 +186,11 @@ type Cookie struct {
ConditionalSameSiteNone bool `json:"conditional-samesite-none"` ConditionalSameSiteNone bool `json:"conditional-samesite-none"`
} }
type affinity struct {
r resolver.Resolver
annotationConfig parser.Annotation
}
// cookieAffinityParse gets the annotation values related to Cookie Affinity // cookieAffinityParse gets the annotation values related to Cookie Affinity
// It also sets default values when no value or incorrect value is found // It also sets default values when no value or incorrect value is found
func (a affinity) cookieAffinityParse(ing *networking.Ingress) *Cookie { func (a affinity) cookieAffinityParse(ing *networking.Ingress) *Cookie {
@ -252,11 +257,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
} }
} }
type affinity struct {
r resolver.Resolver
annotationConfig parser.Annotation
}
// ParseAnnotations parses the annotations contained in the ingress // ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the affinity directives // rule used to configure the affinity directives
func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) { func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
@ -279,11 +279,10 @@ func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
} }
switch at { switch at {
case "cookie": case cookieAffinity:
cookie = a.cookieAffinityParse(ing) cookie = a.cookieAffinityParse(ing)
default: default:
klog.V(3).InfoS("No default affinity found", "ingress", ing.Name) klog.V(3).InfoS("No default affinity found", "ingress", ing.Name)
} }
return &Config{ return &Config{

View file

@ -83,7 +83,11 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieSecure)] = "true" data[parser.GetAnnotationWithPrefix(annotationAffinityCookieSecure)] = "true"
ing.SetAnnotations(data) ing.SetAnnotations(data)
affin, _ := NewParser(&resolver.Mock{}).Parse(ing) affin, err := NewParser(&resolver.Mock{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing annotations: %v", err)
}
nginxAffinity, ok := affin.(*Config) nginxAffinity, ok := affin.(*Config)
if !ok { if !ok {
t.Errorf("expected a Config type") t.Errorf("expected a Config type")

View file

@ -54,6 +54,7 @@ func TestParse(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations) ing.SetAnnotations(testCase.annotations)
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
result, _ := ap.Parse(ing) result, _ := ap.Parse(ing)
if result != testCase.expected { if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)

View file

@ -31,10 +31,8 @@ const (
sslCipherAnnotation = "ssl-ciphers" sslCipherAnnotation = "ssl-ciphers"
) )
var ( // Should cover something like "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"
// Should cover something like "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP" var regexValidSSLCipher = regexp.MustCompile(`^[A-Za-z0-9!:+\-]*$`)
regexValidSSLCipher = regexp.MustCompile(`^[A-Za-z0-9!:+\-]*$`)
)
var sslCipherAnnotations = parser.Annotation{ var sslCipherAnnotations = parser.Annotation{
Group: "backend", Group: "backend",
@ -47,7 +45,7 @@ var sslCipherAnnotations = parser.Annotation{
This configuration specifies that server ciphers should be preferred over client ciphers when using the SSLv3 and TLS protocols.`, This configuration specifies that server ciphers should be preferred over client ciphers when using the SSLv3 and TLS protocols.`,
}, },
sslCipherAnnotation: { sslCipherAnnotation: {
Validator: parser.ValidateRegex(*regexValidSSLCipher, true), Validator: parser.ValidateRegex(regexValidSSLCipher, true),
Scope: parser.AnnotationScopeIngress, Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskLow, Risk: parser.AnnotationRiskLow,
Documentation: `Using this annotation will set the ssl_ciphers directive at the server level. This configuration is active for all the paths in the host.`, Documentation: `Using this annotation will set the ssl_ciphers directive at the server level. This configuration is active for all the paths in the host.`,
@ -104,7 +102,7 @@ func (sc sslCipher) GetDocumentation() parser.AnnotationFields {
return sc.annotationConfig.Annotations return sc.annotationConfig.Annotations
} }
func (a sslCipher) Validate(anns map[string]string) error { func (sc sslCipher) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(sc.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, sslCipherAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, sslCipherAnnotations.Annotations)
} }

View file

@ -42,8 +42,11 @@ func TestParse(t *testing.T) {
expectErr bool expectErr bool
}{ }{
{map[string]string{annotationSSLCiphers: "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"}, Config{"ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP", ""}, false}, {map[string]string{annotationSSLCiphers: "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"}, Config{"ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP", ""}, false},
{map[string]string{annotationSSLCiphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"}, {
Config{"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256", ""}, false}, map[string]string{annotationSSLCiphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"},
Config{"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256", ""},
false,
},
{map[string]string{annotationSSLCiphers: ""}, Config{"", ""}, false}, {map[string]string{annotationSSLCiphers: ""}, Config{"", ""}, false},
{map[string]string{annotationSSLPreferServerCiphers: "true"}, Config{"", "on"}, false}, {map[string]string{annotationSSLPreferServerCiphers: "true"}, Config{"", "on"}, false},
{map[string]string{annotationSSLPreferServerCiphers: "false"}, Config{"", "off"}, false}, {map[string]string{annotationSSLPreferServerCiphers: "false"}, Config{"", "off"}, false},

View file

@ -47,7 +47,8 @@ type sslpt struct {
// NewParser creates a new SSL passthrough annotation parser // NewParser creates a new SSL passthrough annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation { func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return sslpt{r: r, return sslpt{
r: r,
annotationConfig: sslPassthroughAnnotations, annotationConfig: sslPassthroughAnnotations,
} }
} }

View file

@ -38,7 +38,8 @@ func TestParse(t *testing.T) {
annotations map[string]string annotations map[string]string
expected string expected string
}{ }{
{map[string]string{annotation: "server { listen: 8000; proxy_pass 127.0.0.1:80}"}, {
map[string]string{annotation: "server { listen: 8000; proxy_pass 127.0.0.1:80}"},
"server { listen: 8000; proxy_pass 127.0.0.1:80}", "server { listen: 8000; proxy_pass 127.0.0.1:80}",
}, },
{map[string]string{annotation: "false"}, "false"}, {map[string]string{annotation: "false"}, "false"},
@ -56,6 +57,7 @@ func TestParse(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations) ing.SetAnnotations(testCase.annotations)
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
result, _ := ap.Parse(ing) result, _ := ap.Parse(ing)
if result != testCase.expected { if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)

View file

@ -41,7 +41,7 @@ var upstreamHashByAnnotations = parser.Annotation{
Group: "backend", Group: "backend",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
upstreamHashByAnnotation: { upstreamHashByAnnotation: {
Validator: parser.ValidateRegex(*hashByRegex, true), Validator: parser.ValidateRegex(hashByRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskHigh, // High, this annotation allows accessing NGINX variables Risk: parser.AnnotationRiskHigh, // High, this annotation allows accessing NGINX variables
Documentation: `This annotation defines the nginx variable, text value or any combination thereof to use for consistent hashing. Documentation: `This annotation defines the nginx variable, text value or any combination thereof to use for consistent hashing.

View file

@ -31,7 +31,7 @@ var xForwardedForAnnotations = parser.Annotation{
Group: "backend", Group: "backend",
Annotations: parser.AnnotationFields{ Annotations: parser.AnnotationFields{
xForwardedForPrefixAnnotation: { xForwardedForPrefixAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true), Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation, Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, // Low, as it allows regexes but on a very limited set Risk: parser.AnnotationRiskLow, // Low, as it allows regexes but on a very limited set
Documentation: `This annotation can be used to add the non-standard X-Forwarded-Prefix header to the upstream request with a string value`, Documentation: `This annotation can be used to add the non-standard X-Forwarded-Prefix header to the upstream request with a string value`,
@ -54,15 +54,15 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule // Parse parses the annotations contained in the ingress rule
// used to add an x-forwarded-prefix header to the request // used to add an x-forwarded-prefix header to the request
func (cbbs xforwardedprefix) Parse(ing *networking.Ingress) (interface{}, error) { func (x xforwardedprefix) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation(xForwardedForPrefixAnnotation, ing, cbbs.annotationConfig.Annotations) return parser.GetStringAnnotation(xForwardedForPrefixAnnotation, ing, x.annotationConfig.Annotations)
} }
func (cbbs xforwardedprefix) GetDocumentation() parser.AnnotationFields { func (x xforwardedprefix) GetDocumentation() parser.AnnotationFields {
return cbbs.annotationConfig.Annotations return x.annotationConfig.Annotations
} }
func (a xforwardedprefix) Validate(anns map[string]string) error { func (x xforwardedprefix) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel) maxrisk := parser.StringRiskToRisk(x.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, xForwardedForAnnotations.Annotations) return parser.CheckAnnotationRisk(anns, maxrisk, xForwardedForAnnotations.Annotations)
} }

View file

@ -54,6 +54,7 @@ func TestParse(t *testing.T) {
for _, testCase := range testCases { for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations) ing.SetAnnotations(testCase.annotations)
//nolint:errcheck // Ignore the error since invalid cases will be checked with expected results
result, _ := ap.Parse(ing) result, _ := ap.Parse(ing)
if result != testCase.expected { if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)

View file

@ -100,7 +100,7 @@ func matchHostnames(pattern, host string) bool {
host = strings.TrimSuffix(host, ".") host = strings.TrimSuffix(host, ".")
pattern = strings.TrimSuffix(pattern, ".") pattern = strings.TrimSuffix(pattern, ".")
if len(pattern) == 0 || len(host) == 0 { if pattern == "" || host == "" {
return false return false
} }

View file

@ -29,7 +29,7 @@ import (
) )
// Name returns the healthcheck name // Name returns the healthcheck name
func (n NGINXController) Name() string { func (n *NGINXController) Name() string {
return "nginx-ingress-controller" return "nginx-ingress-controller"
} }

View file

@ -32,7 +32,7 @@ import (
) )
func TestNginxCheck(t *testing.T) { func TestNginxCheck(t *testing.T) {
var tests = []struct { tests := []struct {
healthzPath string healthzPath string
}{ }{
{"/healthz"}, {"/healthz"},
@ -42,7 +42,6 @@ func TestNginxCheck(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
testName := fmt.Sprintf("health path: %s", tt.healthzPath) testName := fmt.Sprintf("health path: %s", tt.healthzPath)
t.Run(testName, func(t *testing.T) { t.Run(testName, func(t *testing.T) {
mux := http.NewServeMux() mux := http.NewServeMux()
listener, err := tryListen("tcp", fmt.Sprintf(":%v", nginx.StatusPort)) listener, err := tryListen("tcp", fmt.Sprintf(":%v", nginx.StatusPort))
@ -50,7 +49,7 @@ func TestNginxCheck(t *testing.T) {
t.Fatalf("creating tcp listener: %s", err) t.Fatalf("creating tcp listener: %s", err)
} }
defer listener.Close() defer listener.Close()
//nolint:gosec // Ignore not configured ReadHeaderTimeout in testing
server := &httptest.Server{ server := &httptest.Server{
Listener: listener, Listener: listener,
Config: &http.Server{ Config: &http.Server{
@ -103,10 +102,10 @@ func TestNginxCheck(t *testing.T) {
} }
}() }()
go func() { go func() {
cmd.Wait() //nolint:errcheck cmd.Wait() //nolint:errcheck // Ignore the error
}() }()
if _, err := pidFile.Write([]byte(fmt.Sprintf("%v", pid))); err != nil { if _, err := fmt.Fprintf(pidFile, "%v", pid); err != nil {
t.Errorf("unexpected error writing the pid file: %v", err) t.Errorf("unexpected error writing the pid file: %v", err)
} }
@ -121,7 +120,7 @@ func TestNginxCheck(t *testing.T) {
}) })
// pollute pid file // pollute pid file
pidFile.Write([]byte("999999")) //nolint:errcheck pidFile.WriteString("999999") //nolint:errcheck // Ignore the error
pidFile.Close() pidFile.Close()
t.Run("bad pid", func(t *testing.T) { t.Run("bad pid", func(t *testing.T) {
@ -134,7 +133,7 @@ func TestNginxCheck(t *testing.T) {
} }
func callHealthz(expErr bool, healthzPath string, mux *http.ServeMux) error { func callHealthz(expErr bool, healthzPath string, mux *http.ServeMux) error {
req, err := http.NewRequest(http.MethodGet, healthzPath, nil) req, err := http.NewRequest(http.MethodGet, healthzPath, http.NoBody)
if err != nil { if err != nil {
return fmt.Errorf("healthz error: %v", err) return fmt.Errorf("healthz error: %v", err)
} }

View file

@ -29,10 +29,8 @@ import (
"k8s.io/ingress-nginx/pkg/util/runtime" "k8s.io/ingress-nginx/pkg/util/runtime"
) )
var ( // EnableSSLChainCompletion Autocomplete SSL certificate chains with missing intermediate CA certificates.
// EnableSSLChainCompletion Autocomplete SSL certificate chains with missing intermediate CA certificates. var EnableSSLChainCompletion = false
EnableSSLChainCompletion = false
)
const ( const (
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size // http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
@ -91,7 +89,7 @@ const (
// Configuration represents the content of nginx.conf file // Configuration represents the content of nginx.conf file
type Configuration struct { type Configuration struct {
defaults.Backend `json:",squash"` //nolint:staticcheck defaults.Backend `json:",squash"` //nolint:staticcheck // Ignore unknown JSON option "squash" error
// AllowSnippetAnnotations enable users to add their own snippets via ingress annotation. // AllowSnippetAnnotations enable users to add their own snippets via ingress annotation.
// If disabled, only snippets added via ConfigMap are added to ingress. // If disabled, only snippets added via ConfigMap are added to ingress.
@ -141,9 +139,9 @@ type Configuration struct {
// By default access logs go to /var/log/nginx/access.log // By default access logs go to /var/log/nginx/access.log
AccessLogPath string `json:"access-log-path,omitempty"` AccessLogPath string `json:"access-log-path,omitempty"`
// HttpAccessLogPath sets the path of the access logs for http context globally if enabled // HTTPAccessLogPath sets the path of the access logs for http context globally if enabled
// http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log // http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log
HttpAccessLogPath string `json:"http-access-log-path,omitempty"` HTTPAccessLogPath string `json:"http-access-log-path,omitempty"`
// StreamAccessLogPath sets the path of the access logs for stream context globally if enabled // StreamAccessLogPath sets the path of the access logs for stream context globally if enabled
// http://nginx.org/en/docs/stream/ngx_stream_log_module.html#access_log // http://nginx.org/en/docs/stream/ngx_stream_log_module.html#access_log
@ -230,19 +228,19 @@ type Configuration struct {
// https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_field_size // https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_field_size
// HTTP2MaxFieldSize Limits the maximum size of an HPACK-compressed request header field // HTTP2MaxFieldSize Limits the maximum size of an HPACK-compressed request header field
// NOTE: Deprecated // Deprecated: HTTP2MaxFieldSize is deprecated.
HTTP2MaxFieldSize string `json:"http2-max-field-size,omitempty"` HTTP2MaxFieldSize string `json:"http2-max-field-size,omitempty"`
// https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_header_size // https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_header_size
// HTTP2MaxHeaderSize Limits the maximum size of the entire request header list after HPACK decompression // HTTP2MaxHeaderSize Limits the maximum size of the entire request header list after HPACK decompression
// NOTE: Deprecated // Deprecated: HTTP2MaxHeaderSize is deprecated.
HTTP2MaxHeaderSize string `json:"http2-max-header-size,omitempty"` HTTP2MaxHeaderSize string `json:"http2-max-header-size,omitempty"`
// http://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_requests // http://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_requests
// HTTP2MaxRequests Sets the maximum number of requests (including push requests) that can be served // HTTP2MaxRequests Sets the maximum number of requests (including push requests) that can be served
// through one HTTP/2 connection, after which the next client request will lead to connection closing // through one HTTP/2 connection, after which the next client request will lead to connection closing
// and the need of establishing a new connection. // and the need of establishing a new connection.
// NOTE: Deprecated // Deprecated: HTTP2MaxRequests is deprecated.
HTTP2MaxRequests int `json:"http2-max-requests,omitempty"` HTTP2MaxRequests int `json:"http2-max-requests,omitempty"`
// http://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_concurrent_streams // http://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_concurrent_streams
@ -552,7 +550,7 @@ type Configuration struct {
UseForwardedHeaders bool `json:"use-forwarded-headers"` UseForwardedHeaders bool `json:"use-forwarded-headers"`
// Sets whether to enable the real ip module // Sets whether to enable the real ip module
EnableRealIp bool `json:"enable-real-ip"` EnableRealIP bool `json:"enable-real-ip"`
// Sets the header field for identifying the originating IP address of a client // Sets the header field for identifying the originating IP address of a client
// Default is X-Forwarded-For // Default is X-Forwarded-For
@ -621,7 +619,7 @@ type Configuration struct {
// Default: 0.01 // Default: 0.01
OtelSamplerRatio float32 `json:"otel-sampler-ratio"` OtelSamplerRatio float32 `json:"otel-sampler-ratio"`
//OtelSamplerParentBased specifies the parent based sampler to be use for any traces created // OtelSamplerParentBased specifies the parent based sampler to be use for any traces created
// Default: true // Default: true
OtelSamplerParentBased bool `json:"otel-sampler-parent-based"` OtelSamplerParentBased bool `json:"otel-sampler-parent-based"`
@ -891,7 +889,7 @@ func NewDefault() Configuration {
EnableUnderscoresInHeaders: false, EnableUnderscoresInHeaders: false,
ErrorLogLevel: errorLevel, ErrorLogLevel: errorLevel,
UseForwardedHeaders: false, UseForwardedHeaders: false,
EnableRealIp: false, EnableRealIP: false,
ForwardedForHeader: "X-Forwarded-For", ForwardedForHeader: "X-Forwarded-For",
ComputeFullForwardedFor: false, ComputeFullForwardedFor: false,
ProxyAddOriginalURIHeader: false, ProxyAddOriginalURIHeader: false,
@ -1039,41 +1037,41 @@ func NewDefault() Configuration {
// TemplateConfig contains the nginx configuration to render the file nginx.conf // TemplateConfig contains the nginx configuration to render the file nginx.conf
type TemplateConfig struct { type TemplateConfig struct {
ProxySetHeaders map[string]string ProxySetHeaders map[string]string `json:"ProxySetHeaders"`
AddHeaders map[string]string AddHeaders map[string]string `json:"AddHeaders"`
BacklogSize int BacklogSize int `json:"BacklogSize"`
Backends []*ingress.Backend Backends []*ingress.Backend `json:"Backends"`
PassthroughBackends []*ingress.SSLPassthroughBackend PassthroughBackends []*ingress.SSLPassthroughBackend `json:"PassthroughBackends"`
Servers []*ingress.Server Servers []*ingress.Server `json:"Servers"`
TCPBackends []ingress.L4Service TCPBackends []ingress.L4Service `json:"TCPBackends"`
UDPBackends []ingress.L4Service UDPBackends []ingress.L4Service `json:"UDPBackends"`
HealthzURI string HealthzURI string `json:"HealthzURI"`
Cfg Configuration Cfg Configuration `json:"Cfg"`
IsIPV6Enabled bool IsIPV6Enabled bool `json:"IsIPV6Enabled"`
IsSSLPassthroughEnabled bool IsSSLPassthroughEnabled bool `json:"IsSSLPassthroughEnabled"`
NginxStatusIpv4Whitelist []string NginxStatusIpv4Whitelist []string `json:"NginxStatusIpv4Whitelist"`
NginxStatusIpv6Whitelist []string NginxStatusIpv6Whitelist []string `json:"NginxStatusIpv6Whitelist"`
RedirectServers interface{} RedirectServers interface{} `json:"RedirectServers"`
ListenPorts *ListenPorts ListenPorts *ListenPorts `json:"ListenPorts"`
PublishService *apiv1.Service PublishService *apiv1.Service `json:"PublishService"`
EnableMetrics bool EnableMetrics bool `json:"EnableMetrics"`
MaxmindEditionFiles *[]string MaxmindEditionFiles *[]string `json:"MaxmindEditionFiles"`
MonitorMaxBatchSize int MonitorMaxBatchSize int `json:"MonitorMaxBatchSize"`
PID string PID string `json:"PID"`
StatusPath string StatusPath string `json:"StatusPath"`
StatusPort int StatusPort int `json:"StatusPort"`
StreamPort int StreamPort int `json:"StreamPort"`
StreamSnippets []string StreamSnippets []string `json:"StreamSnippets"`
} }
// ListenPorts describe the ports required to run the // ListenPorts describe the ports required to run the
// NGINX Ingress controller // NGINX Ingress controller
type ListenPorts struct { type ListenPorts struct {
HTTP int HTTP int `json:"HTTP"`
HTTPS int HTTPS int `json:"HTTPS"`
Health int Health int `json:"Health"`
Default int Default int `json:"Default"`
SSLProxy int SSLProxy int `json:"SSLProxy"`
} }
// GlobalExternalAuth describe external authentication configuration for the // GlobalExternalAuth describe external authentication configuration for the

View file

@ -114,7 +114,7 @@ type Configuration struct {
DisableCatchAll bool DisableCatchAll bool
IngressClassConfiguration *ingressclass.IngressClassConfiguration IngressClassConfiguration *ingressclass.Configuration
ValidationWebhook string ValidationWebhook string
ValidationWebhookCertPath string ValidationWebhookCertPath string
@ -143,7 +143,7 @@ type Configuration struct {
func getIngressPodZone(svc *apiv1.Service) string { func getIngressPodZone(svc *apiv1.Service) string {
svcKey := k8s.MetaNamespaceKey(svc) svcKey := k8s.MetaNamespaceKey(svc)
if svcZoneAnnotation, ok := svc.ObjectMeta.GetAnnotations()[apiv1.AnnotationTopologyMode]; ok { if svcZoneAnnotation, ok := svc.ObjectMeta.GetAnnotations()[apiv1.AnnotationTopologyMode]; ok {
if strings.ToLower(svcZoneAnnotation) == "auto" { if strings.EqualFold(svcZoneAnnotation, "auto") {
if foundZone, ok := k8s.IngressNodeDetails.GetLabels()[apiv1.LabelTopologyZone]; ok { if foundZone, ok := k8s.IngressNodeDetails.GetLabels()[apiv1.LabelTopologyZone]; ok {
klog.V(3).Infof("Svc has topology aware annotation enabled, try to use zone %q where controller pod is running for Service %q ", foundZone, svcKey) klog.V(3).Infof("Svc has topology aware annotation enabled, try to use zone %q where controller pod is running for Service %q ", foundZone, svcKey)
return foundZone return foundZone
@ -154,7 +154,7 @@ func getIngressPodZone(svc *apiv1.Service) string {
} }
// GetPublishService returns the Service used to set the load-balancer status of Ingresses. // GetPublishService returns the Service used to set the load-balancer status of Ingresses.
func (n NGINXController) GetPublishService() *apiv1.Service { func (n *NGINXController) GetPublishService() *apiv1.Service {
s, err := n.store.GetService(n.cfg.PublishService) s, err := n.store.GetService(n.cfg.PublishService)
if err != nil { if err != nil {
return nil return nil
@ -189,13 +189,16 @@ func (n *NGINXController) syncIngress(interface{}) error {
if !utilingress.IsDynamicConfigurationEnough(pcfg, n.runningConfig) { if !utilingress.IsDynamicConfigurationEnough(pcfg, n.runningConfig) {
klog.InfoS("Configuration changes detected, backend reload required") klog.InfoS("Configuration changes detected, backend reload required")
hash, _ := hashstructure.Hash(pcfg, hashstructure.FormatV1, &hashstructure.HashOptions{ hash, err := hashstructure.Hash(pcfg, hashstructure.FormatV1, &hashstructure.HashOptions{
TagName: "json", TagName: "json",
}) })
if err != nil {
klog.Errorf("unexpected error hashing configuration: %v", err)
}
pcfg.ConfigurationChecksum = fmt.Sprintf("%v", hash) pcfg.ConfigurationChecksum = fmt.Sprintf("%v", hash)
err := n.OnUpdate(*pcfg) err = n.OnUpdate(*pcfg)
if err != nil { if err != nil {
n.metricCollector.IncReloadErrorCount() n.metricCollector.IncReloadErrorCount()
n.metricCollector.ConfigSuccess(hash, false) n.metricCollector.ConfigSuccess(hash, false)
@ -263,7 +266,7 @@ func (n *NGINXController) syncIngress(interface{}) error {
func (n *NGINXController) CheckWarning(ing *networking.Ingress) ([]string, error) { func (n *NGINXController) CheckWarning(ing *networking.Ingress) ([]string, error) {
warnings := make([]string, 0) warnings := make([]string, 0)
var deprecatedAnnotations = sets.NewString() deprecatedAnnotations := sets.NewString()
deprecatedAnnotations.Insert( deprecatedAnnotations.Insert(
"enable-influxdb", "enable-influxdb",
"influxdb-measurement", "influxdb-measurement",
@ -335,7 +338,7 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
} }
if n.cfg.DisableCatchAll && ing.Spec.DefaultBackend != nil { if n.cfg.DisableCatchAll && ing.Spec.DefaultBackend != nil {
return fmt.Errorf("This deployment is trying to create a catch-all ingress while DisableCatchAll flag is set to true. Remove '.spec.defaultBackend' or set DisableCatchAll flag to false.") return fmt.Errorf("this deployment is trying to create a catch-all ingress while DisableCatchAll flag is set to true. Remove '.spec.defaultBackend' or set DisableCatchAll flag to false")
} }
startRender := time.Now().UnixNano() / 1000000 startRender := time.Now().UnixNano() / 1000000
cfg := n.store.GetBackendConfiguration() cfg := n.store.GetBackendConfiguration()
@ -355,10 +358,9 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
} }
for key, value := range ing.ObjectMeta.GetAnnotations() { for key, value := range ing.ObjectMeta.GetAnnotations() {
if parser.AnnotationsPrefix != parser.DefaultAnnotationsPrefix { if parser.AnnotationsPrefix != parser.DefaultAnnotationsPrefix {
if strings.HasPrefix(key, fmt.Sprintf("%s/", parser.DefaultAnnotationsPrefix)) { if strings.HasPrefix(key, fmt.Sprintf("%s/", parser.DefaultAnnotationsPrefix)) {
return fmt.Errorf("This deployment has a custom annotation prefix defined. Use '%s' instead of '%s'", parser.AnnotationsPrefix, parser.DefaultAnnotationsPrefix) return fmt.Errorf("this deployment has a custom annotation prefix defined. Use '%s' instead of '%s'", parser.AnnotationsPrefix, parser.DefaultAnnotationsPrefix)
} }
} }
@ -374,10 +376,9 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
return fmt.Errorf("%s annotation cannot be used. Snippet directives are disabled by the Ingress administrator", key) return fmt.Errorf("%s annotation cannot be used. Snippet directives are disabled by the Ingress administrator", key)
} }
if len(cfg.GlobalRateLimitMemcachedHost) == 0 && strings.HasPrefix(key, fmt.Sprintf("%s/%s", parser.AnnotationsPrefix, "global-rate-limit")) { if cfg.GlobalRateLimitMemcachedHost == "" && strings.HasPrefix(key, fmt.Sprintf("%s/%s", parser.AnnotationsPrefix, "global-rate-limit")) {
return fmt.Errorf("'global-rate-limit*' annotations require 'global-rate-limit-memcached-host' settings configured in the global configmap") return fmt.Errorf("'global-rate-limit*' annotations require 'global-rate-limit-memcached-host' settings configured in the global configmap")
} }
} }
k8s.SetDefaultNGINXPathType(ing) k8s.SetDefaultNGINXPathType(ing)
@ -401,7 +402,7 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
startTest := time.Now().UnixNano() / 1000000 startTest := time.Now().UnixNano() / 1000000
_, servers, pcfg := n.getConfiguration(ings) _, servers, pcfg := n.getConfiguration(ings)
err = checkOverlap(ing, allIngresses, servers) err = checkOverlap(ing, servers)
if err != nil { if err != nil {
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name) n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
return err return err
@ -452,7 +453,7 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr
return []ingress.L4Service{} return []ingress.L4Service{}
} }
var svcs []ingress.L4Service svcs := make([]ingress.L4Service, 0, len(configmap.Data))
var svcProxyProtocol ingress.ProxyProtocol var svcProxyProtocol ingress.ProxyProtocol
rp := []int{ rp := []int{
@ -489,10 +490,10 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr
svcProxyProtocol.Encode = false svcProxyProtocol.Encode = false
// Proxy Protocol is only compatible with TCP Services // Proxy Protocol is only compatible with TCP Services
if len(nsSvcPort) >= 3 && proto == apiv1.ProtocolTCP { if len(nsSvcPort) >= 3 && proto == apiv1.ProtocolTCP {
if len(nsSvcPort) >= 3 && strings.ToUpper(nsSvcPort[2]) == "PROXY" { if len(nsSvcPort) >= 3 && strings.EqualFold(nsSvcPort[2], "PROXY") {
svcProxyProtocol.Decode = true svcProxyProtocol.Decode = true
} }
if len(nsSvcPort) == 4 && strings.ToUpper(nsSvcPort[3]) == "PROXY" { if len(nsSvcPort) == 4 && strings.EqualFold(nsSvcPort[3], "PROXY") {
svcProxyProtocol.Encode = true svcProxyProtocol.Encode = true
} }
} }
@ -532,6 +533,7 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr
klog.V(3).Infof("Searching Endpoints with %v port number %d for Service %q", proto, targetPort, nsName) klog.V(3).Infof("Searching Endpoints with %v port number %d for Service %q", proto, targetPort, nsName)
for i := range svc.Spec.Ports { for i := range svc.Spec.Ports {
sp := svc.Spec.Ports[i] sp := svc.Spec.Ports[i]
//nolint:gosec // Ignore G109 error
if sp.Port == int32(targetPort) { if sp.Port == int32(targetPort) {
if sp.Protocol == proto { if sp.Protocol == proto {
endps = getEndpointsFromSlices(svc, &sp, proto, zone, n.store.GetServiceEndpointsSlices) endps = getEndpointsFromSlices(svc, &sp, proto, zone, n.store.GetServiceEndpointsSlices)
@ -574,7 +576,7 @@ func (n *NGINXController) getDefaultUpstream() *ingress.Backend {
} }
svcKey := n.cfg.DefaultService svcKey := n.cfg.DefaultService
if len(svcKey) == 0 { if svcKey == "" {
upstream.Endpoints = append(upstream.Endpoints, n.DefaultEndpoint()) upstream.Endpoints = append(upstream.Endpoints, n.DefaultEndpoint())
return upstream return upstream
} }
@ -690,13 +692,14 @@ func dropSnippetDirectives(anns *annotations.Ingress, ingKey string) {
klog.V(3).Infof("Ingress %q tried to use stream-snippet and the annotation is disabled by the admin. Removing the annotation", ingKey) klog.V(3).Infof("Ingress %q tried to use stream-snippet and the annotation is disabled by the admin. Removing the annotation", ingKey)
anns.StreamSnippet = "" anns.StreamSnippet = ""
} }
} }
} }
// getBackendServers returns a list of Upstream and Server to be used by the // getBackendServers returns a list of Upstream and Server to be used by the
// backend. An upstream can be used in multiple servers if the namespace, // backend. An upstream can be used in multiple servers if the namespace,
// service name and port are the same. // service name and port are the same.
//
//nolint:gocyclo // Ignore function complexity error
func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*ingress.Backend, []*ingress.Server) { func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*ingress.Backend, []*ingress.Server) {
du := n.getDefaultUpstream() du := n.getDefaultUpstream()
upstreams := n.createUpstreams(ingresses, du) upstreams := n.createUpstreams(ingresses, du)
@ -1030,7 +1033,7 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
// configure traffic shaping for canary // configure traffic shaping for canary
if anns.Canary.Enabled { if anns.Canary.Enabled {
upstreams[defBackend].NoServer = true upstreams[defBackend].NoServer = true
upstreams[defBackend].TrafficShapingPolicy = newTrafficShapingPolicy(anns.Canary) upstreams[defBackend].TrafficShapingPolicy = newTrafficShapingPolicy(&anns.Canary)
} }
if len(upstreams[defBackend].Endpoints) == 0 { if len(upstreams[defBackend].Endpoints) == 0 {
@ -1095,7 +1098,7 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
// configure traffic shaping for canary // configure traffic shaping for canary
if anns.Canary.Enabled { if anns.Canary.Enabled {
upstreams[name].NoServer = true upstreams[name].NoServer = true
upstreams[name].TrafficShapingPolicy = newTrafficShapingPolicy(anns.Canary) upstreams[name].TrafficShapingPolicy = newTrafficShapingPolicy(&anns.Canary)
} }
if len(upstreams[name].Endpoints) == 0 { if len(upstreams[name].Endpoints) == 0 {
@ -1206,7 +1209,6 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres
if strconv.Itoa(int(servicePort.Port)) == backendPort || if strconv.Itoa(int(servicePort.Port)) == backendPort ||
servicePort.TargetPort.String() == backendPort || servicePort.TargetPort.String() == backendPort ||
servicePort.Name == backendPort { servicePort.Name == backendPort {
endps := getEndpointsFromSlices(svc, &servicePort, apiv1.ProtocolTCP, zone, n.store.GetServiceEndpointsSlices) endps := getEndpointsFromSlices(svc, &servicePort, apiv1.ProtocolTCP, zone, n.store.GetServiceEndpointsSlices)
if len(endps) == 0 { if len(endps) == 0 {
klog.Warningf("Service %q does not have any active Endpoint.", svcKey) klog.Warningf("Service %q does not have any active Endpoint.", svcKey)
@ -1239,8 +1241,8 @@ func (n *NGINXController) getDefaultSSLCertificate() *ingress.SSLCert {
// one root location, which uses a default backend if left unspecified. // one root location, which uses a default backend if left unspecified.
func (n *NGINXController) createServers(data []*ingress.Ingress, func (n *NGINXController) createServers(data []*ingress.Ingress,
upstreams map[string]*ingress.Backend, upstreams map[string]*ingress.Backend,
du *ingress.Backend) map[string]*ingress.Server { du *ingress.Backend,
) map[string]*ingress.Server {
servers := make(map[string]*ingress.Server, len(data)) servers := make(map[string]*ingress.Server, len(data))
allAliases := make(map[string][]string, len(data)) allAliases := make(map[string][]string, len(data))
@ -1282,7 +1284,8 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
Rewrite: false, Rewrite: false,
}, },
}, },
}} },
}
// initialize all other servers // initialize all other servers
for _, ing := range data { for _, ing := range data {
@ -1532,7 +1535,7 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
} }
// OK to merge canary ingresses iff there exists one or more ingresses to potentially merge into // OK to merge canary ingresses iff there exists one or more ingresses to potentially merge into
func nonCanaryIngressExists(ingresses []*ingress.Ingress, canaryIngresses []*ingress.Ingress) bool { func nonCanaryIngressExists(ingresses, canaryIngresses []*ingress.Ingress) bool {
return len(ingresses)-len(canaryIngresses) > 0 return len(ingresses)-len(canaryIngresses) > 0
} }
@ -1540,12 +1543,12 @@ func nonCanaryIngressExists(ingresses []*ingress.Ingress, canaryIngresses []*ing
// 1) names of backends do not match and canary doesn't merge into itself // 1) names of backends do not match and canary doesn't merge into itself
// 2) primary name is not the default upstream // 2) primary name is not the default upstream
// 3) the primary has a server // 3) the primary has a server
func canMergeBackend(primary *ingress.Backend, alternative *ingress.Backend) bool { func canMergeBackend(primary, alternative *ingress.Backend) bool {
return alternative != nil && primary.Name != alternative.Name && primary.Name != defUpstreamName && !primary.NoServer return alternative != nil && primary.Name != alternative.Name && primary.Name != defUpstreamName && !primary.NoServer
} }
// Performs the merge action and checks to ensure that one two alternative backends do not merge into each other // Performs the merge action and checks to ensure that one two alternative backends do not merge into each other
func mergeAlternativeBackend(ing *ingress.Ingress, priUps *ingress.Backend, altUps *ingress.Backend) bool { func mergeAlternativeBackend(ing *ingress.Ingress, priUps, altUps *ingress.Backend) bool {
if priUps.NoServer { if priUps.NoServer {
klog.Warningf("unable to merge alternative backend %v into primary backend %v because %v is a primary backend", klog.Warningf("unable to merge alternative backend %v into primary backend %v because %v is a primary backend",
altUps.Name, priUps.Name, priUps.Name) altUps.Name, priUps.Name, priUps.Name)
@ -1563,8 +1566,7 @@ func mergeAlternativeBackend(ing *ingress.Ingress, priUps *ingress.Backend, altU
priUps.SessionAffinity.DeepCopyInto(&altUps.SessionAffinity) priUps.SessionAffinity.DeepCopyInto(&altUps.SessionAffinity)
} }
priUps.AlternativeBackends = priUps.AlternativeBackends = append(priUps.AlternativeBackends, altUps.Name)
append(priUps.AlternativeBackends, altUps.Name)
return true return true
} }
@ -1574,8 +1576,8 @@ func mergeAlternativeBackend(ing *ingress.Ingress, priUps *ingress.Backend, altU
// to a backend's alternative list. // to a backend's alternative list.
// If no match is found, then the serverless backend is deleted. // If no match is found, then the serverless backend is deleted.
func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingress.Backend, func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingress.Backend,
servers map[string]*ingress.Server) { servers map[string]*ingress.Server,
) {
// merge catch-all alternative backends // merge catch-all alternative backends
if ing.Spec.DefaultBackend != nil { if ing.Spec.DefaultBackend != nil {
upsName := upstreamName(ing.Namespace, ing.Spec.DefaultBackend.Service) upsName := upstreamName(ing.Namespace, ing.Spec.DefaultBackend.Service)
@ -1585,7 +1587,6 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
if altUps == nil { if altUps == nil {
klog.Warningf("alternative backend %s has already been removed", upsName) klog.Warningf("alternative backend %s has already been removed", upsName)
} else { } else {
merged := false merged := false
altEqualsPri := false altEqualsPri := false
@ -1676,8 +1677,8 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
// extractTLSSecretName returns the name of the Secret containing a SSL // extractTLSSecretName returns the name of the Secret containing a SSL
// certificate for the given host name, or an empty string. // certificate for the given host name, or an empty string.
func extractTLSSecretName(host string, ing *ingress.Ingress, func extractTLSSecretName(host string, ing *ingress.Ingress,
getLocalSSLCert func(string) (*ingress.SSLCert, error)) string { getLocalSSLCert func(string) (*ingress.SSLCert, error),
) string {
if ing == nil { if ing == nil {
return "" return ""
} }
@ -1694,7 +1695,6 @@ func extractTLSSecretName(host string, ing *ingress.Ingress,
// no TLS host matching host name, try each TLS host for matching SAN or CN // no TLS host matching host name, try each TLS host for matching SAN or CN
for _, tls := range ing.Spec.TLS { for _, tls := range ing.Spec.TLS {
if tls.SecretName == "" { if tls.SecretName == "" {
// There's no secretName specified, so it will never be available // There's no secretName specified, so it will never be available
continue continue
@ -1753,6 +1753,7 @@ func externalNamePorts(name string, svc *apiv1.Service) *apiv1.ServicePort {
} }
for _, svcPort := range svc.Spec.Ports { for _, svcPort := range svc.Spec.Ports {
//nolint:gosec // Ignore G109 error
if svcPort.Port != int32(port) { if svcPort.Port != int32(port) {
continue continue
} }
@ -1771,13 +1772,14 @@ func externalNamePorts(name string, svc *apiv1.Service) *apiv1.ServicePort {
// ExternalName without port // ExternalName without port
return &apiv1.ServicePort{ return &apiv1.ServicePort{
Protocol: "TCP", Protocol: "TCP",
//nolint:gosec // Ignore G109 error
Port: int32(port), Port: int32(port),
TargetPort: intstr.FromInt(port), TargetPort: intstr.FromInt(port),
} }
} }
func checkOverlap(ing *networking.Ingress, ingresses []*ingress.Ingress, servers []*ingress.Server) error { func checkOverlap(ing *networking.Ingress, servers []*ingress.Server) error {
for _, rule := range ing.Spec.Rules { for _, rule := range ing.Spec.Rules {
if rule.HTTP == nil { if rule.HTTP == nil {
continue continue
@ -1870,7 +1872,7 @@ func (n *NGINXController) getStreamSnippets(ingresses []*ingress.Ingress) []stri
} }
// newTrafficShapingPolicy creates new ingress.TrafficShapingPolicy instance using canary configuration // newTrafficShapingPolicy creates new ingress.TrafficShapingPolicy instance using canary configuration
func newTrafficShapingPolicy(cfg canary.Config) ingress.TrafficShapingPolicy { func newTrafficShapingPolicy(cfg *canary.Config) ingress.TrafficShapingPolicy {
return ingress.TrafficShapingPolicy{ return ingress.TrafficShapingPolicy{
Weight: cfg.Weight, Weight: cfg.Weight,
WeightTotal: cfg.WeightTotal, WeightTotal: cfg.WeightTotal,

View file

@ -60,51 +60,56 @@ import (
"k8s.io/ingress-nginx/pkg/util/file" "k8s.io/ingress-nginx/pkg/util/file"
) )
const (
exampleBackend = "example-http-svc-1-80"
TRUE = "true"
)
type fakeIngressStore struct { type fakeIngressStore struct {
ingresses []*ingress.Ingress ingresses []*ingress.Ingress
configuration ngx_config.Configuration configuration ngx_config.Configuration
} }
func (fakeIngressStore) GetIngressClass(ing *networking.Ingress, icConfig *ingressclass.IngressClassConfiguration) (string, error) { func (fakeIngressStore) GetIngressClass(_ *networking.Ingress, _ *ingressclass.Configuration) (string, error) {
return "nginx", nil return "nginx", nil
} }
func (fis fakeIngressStore) GetBackendConfiguration() ngx_config.Configuration { func (fis *fakeIngressStore) GetBackendConfiguration() ngx_config.Configuration {
return fis.configuration return fis.configuration
} }
func (fis fakeIngressStore) GetSecurityConfiguration() defaults.SecurityConfiguration { func (fis *fakeIngressStore) GetSecurityConfiguration() defaults.SecurityConfiguration {
return defaults.SecurityConfiguration{ return defaults.SecurityConfiguration{
AnnotationsRiskLevel: fis.configuration.AnnotationsRiskLevel, AnnotationsRiskLevel: fis.configuration.AnnotationsRiskLevel,
AllowCrossNamespaceResources: fis.configuration.AllowCrossNamespaceResources, AllowCrossNamespaceResources: fis.configuration.AllowCrossNamespaceResources,
} }
} }
func (fakeIngressStore) GetConfigMap(key string) (*corev1.ConfigMap, error) { func (fakeIngressStore) GetConfigMap(_ string) (*corev1.ConfigMap, error) {
return nil, fmt.Errorf("test error") return nil, fmt.Errorf("test error")
} }
func (fakeIngressStore) GetSecret(key string) (*corev1.Secret, error) { func (fakeIngressStore) GetSecret(_ string) (*corev1.Secret, error) {
return nil, fmt.Errorf("test error") return nil, fmt.Errorf("test error")
} }
func (fakeIngressStore) GetService(key string) (*corev1.Service, error) { func (fakeIngressStore) GetService(_ string) (*corev1.Service, error) {
return nil, fmt.Errorf("test error") return nil, fmt.Errorf("test error")
} }
func (fakeIngressStore) GetServiceEndpointsSlices(key string) ([]*discoveryv1.EndpointSlice, error) { func (fakeIngressStore) GetServiceEndpointsSlices(_ string) ([]*discoveryv1.EndpointSlice, error) {
return nil, fmt.Errorf("test error") return nil, fmt.Errorf("test error")
} }
func (fis fakeIngressStore) ListIngresses() []*ingress.Ingress { func (fis *fakeIngressStore) ListIngresses() []*ingress.Ingress {
return fis.ingresses return fis.ingresses
} }
func (fis fakeIngressStore) FilterIngresses(ingresses []*ingress.Ingress, filterFunc store.IngressFilterFunc) []*ingress.Ingress { func (fis *fakeIngressStore) FilterIngresses(ingresses []*ingress.Ingress, _ store.IngressFilterFunc) []*ingress.Ingress {
return ingresses return ingresses
} }
func (fakeIngressStore) GetLocalSSLCert(name string) (*ingress.SSLCert, error) { func (fakeIngressStore) GetLocalSSLCert(_ string) (*ingress.SSLCert, error) {
return nil, fmt.Errorf("test error") return nil, fmt.Errorf("test error")
} }
@ -120,7 +125,7 @@ func (fakeIngressStore) GetDefaultBackend() defaults.Backend {
return defaults.Backend{} return defaults.Backend{}
} }
func (fakeIngressStore) Run(stopCh chan struct{}) {} func (fakeIngressStore) Run(_ chan struct{}) {}
type testNginxTestCommand struct { type testNginxTestCommand struct {
t *testing.T t *testing.T
@ -129,7 +134,7 @@ type testNginxTestCommand struct {
err error err error
} }
func (ntc testNginxTestCommand) ExecCommand(args ...string) *exec.Cmd { func (ntc testNginxTestCommand) ExecCommand(_ ...string) *exec.Cmd {
return nil return nil
} }
@ -152,7 +157,7 @@ func (ntc testNginxTestCommand) Test(cfg string) ([]byte, error) {
type fakeTemplate struct{} type fakeTemplate struct{}
func (fakeTemplate) Write(conf ngx_config.TemplateConfig) ([]byte, error) { func (fakeTemplate) Write(conf *ngx_config.TemplateConfig) ([]byte, error) {
r := []byte{} r := []byte{}
for _, s := range conf.Servers { for _, s := range conf.Servers {
if len(r) > 0 { if len(r) > 0 {
@ -196,7 +201,7 @@ func TestCheckIngress(t *testing.T) {
nginx.metricCollector = metric.DummyCollector{} nginx.metricCollector = metric.DummyCollector{}
nginx.t = fakeTemplate{} nginx.t = fakeTemplate{}
nginx.store = fakeIngressStore{ nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{}, ingresses: []*ingress.Ingress{},
} }
@ -226,7 +231,7 @@ func TestCheckIngress(t *testing.T) {
} }
t.Run("When the hostname is updated", func(t *testing.T) { t.Run("When the hostname is updated", func(t *testing.T) {
nginx.store = fakeIngressStore{ nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{ ingresses: []*ingress.Ingress{
{ {
Ingress: *ing, Ingress: *ing,
@ -273,7 +278,7 @@ func TestCheckIngress(t *testing.T) {
}) })
t.Run("When snippets are disabled and user tries to use snippet annotation", func(t *testing.T) { t.Run("When snippets are disabled and user tries to use snippet annotation", func(t *testing.T) {
nginx.store = fakeIngressStore{ nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{}, ingresses: []*ingress.Ingress{},
configuration: ngx_config.Configuration{ configuration: ngx_config.Configuration{
AllowSnippetAnnotations: false, AllowSnippetAnnotations: false,
@ -290,7 +295,7 @@ func TestCheckIngress(t *testing.T) {
}) })
t.Run("When invalid directives are used in annotation values", func(t *testing.T) { t.Run("When invalid directives are used in annotation values", func(t *testing.T) {
nginx.store = fakeIngressStore{ nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{}, ingresses: []*ingress.Ingress{},
configuration: ngx_config.Configuration{ configuration: ngx_config.Configuration{
AnnotationValueWordBlocklist: "invalid_directive, another_directive", AnnotationValueWordBlocklist: "invalid_directive, another_directive",
@ -366,12 +371,11 @@ func TestCheckIngress(t *testing.T) {
} }
func TestCheckWarning(t *testing.T) { func TestCheckWarning(t *testing.T) {
// Ensure no panic with wrong arguments // Ensure no panic with wrong arguments
var nginx = &NGINXController{} nginx := &NGINXController{}
nginx.t = fakeTemplate{} nginx.t = fakeTemplate{}
nginx.store = fakeIngressStore{ nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{}, ingresses: []*ingress.Ingress{},
} }
@ -390,7 +394,7 @@ func TestCheckWarning(t *testing.T) {
}, },
} }
t.Run("when a deprecated annotation is used a warning should be returned", func(t *testing.T) { t.Run("when a deprecated annotation is used a warning should be returned", func(t *testing.T) {
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = "true" ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = TRUE
defer func() { defer func() {
ing.ObjectMeta.Annotations = map[string]string{} ing.ObjectMeta.Annotations = map[string]string{}
}() }()
@ -407,7 +411,6 @@ func TestCheckWarning(t *testing.T) {
}) })
t.Run("When an invalid pathType is used, a warning should be returned", func(t *testing.T) { t.Run("When an invalid pathType is used, a warning should be returned", func(t *testing.T) {
rules := ing.Spec.DeepCopy().Rules rules := ing.Spec.DeepCopy().Rules
ing.Spec.Rules = []networking.IngressRule{ ing.Spec.Rules = []networking.IngressRule{
{ {
@ -443,8 +446,8 @@ func TestCheckWarning(t *testing.T) {
} }
t.Run("adding invalid annotations increases the warning count", func(t *testing.T) { t.Run("adding invalid annotations increases the warning count", func(t *testing.T) {
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = "true" ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = TRUE
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("secure-verify-ca-secret")] = "true" ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("secure-verify-ca-secret")] = TRUE
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("influxdb-host")] = "blabla" ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("influxdb-host")] = "blabla"
defer func() { defer func() {
ing.ObjectMeta.Annotations = map[string]string{} ing.ObjectMeta.Annotations = map[string]string{}
@ -472,6 +475,7 @@ func TestCheckWarning(t *testing.T) {
}) })
} }
//nolint:dupl // Ignore dupl errors for similar test case
func TestMergeAlternativeBackends(t *testing.T) { func TestMergeAlternativeBackends(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
ingress *ingress.Ingress ingress *ingress.Ingress
@ -1537,8 +1541,8 @@ func TestExtractTLSSecretName(t *testing.T) {
} }
} }
//nolint:gocyclo // Ignore function complexity error
func TestGetBackendServers(t *testing.T) { func TestGetBackendServers(t *testing.T) {
testCases := []struct { testCases := []struct {
Ingresses []*ingress.Ingress Ingresses []*ingress.Ingress
Validate func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server) Validate func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server)
@ -2078,7 +2082,7 @@ func TestGetBackendServers(t *testing.T) {
t.Errorf("server hostname should be 'example.com', got '%s'", s.Hostname) t.Errorf("server hostname should be 'example.com', got '%s'", s.Hostname)
} }
if s.Locations[0].Backend != "example-http-svc-1-80" || s.Locations[1].Backend != "example-http-svc-1-80" || s.Locations[2].Backend != "example-http-svc-1-80" { if s.Locations[0].Backend != exampleBackend || s.Locations[1].Backend != exampleBackend || s.Locations[2].Backend != exampleBackend {
t.Errorf("all location backend should be 'example-http-svc-1-80'") t.Errorf("all location backend should be 'example-http-svc-1-80'")
} }
@ -2087,7 +2091,7 @@ func TestGetBackendServers(t *testing.T) {
return return
} }
if upstreams[0].Name != "example-http-svc-1-80" { if upstreams[0].Name != exampleBackend {
t.Errorf("example-http-svc-1-80 should be first upstream, got %s", upstreams[0].Name) t.Errorf("example-http-svc-1-80 should be first upstream, got %s", upstreams[0].Name)
return return
} }
@ -2101,6 +2105,7 @@ func TestGetBackendServers(t *testing.T) {
SetConfigMap: testConfigMap, SetConfigMap: testConfigMap,
}, },
{ {
//nolint:dupl // Ignore dupl errors for similar test case
Ingresses: []*ingress.Ingress{ Ingresses: []*ingress.Ingress{
{ {
Ingress: networking.Ingress{ Ingress: networking.Ingress{
@ -2208,6 +2213,7 @@ func TestGetBackendServers(t *testing.T) {
SetConfigMap: testConfigMap, SetConfigMap: testConfigMap,
}, },
{ {
//nolint:dupl // Ignore dupl errors for similar test case
Ingresses: []*ingress.Ingress{ Ingresses: []*ingress.Ingress{
{ {
Ingress: networking.Ingress{ Ingress: networking.Ingress{
@ -2319,7 +2325,7 @@ func TestGetBackendServers(t *testing.T) {
SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/config", ns), SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/config", ns),
}, },
Data: map[string]string{ Data: map[string]string{
"proxy-ssl-location-only": "true", "proxy-ssl-location-only": TRUE,
}, },
} }
}, },
@ -2380,7 +2386,7 @@ func TestGetBackendServers(t *testing.T) {
SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/config", ns), SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/config", ns),
}, },
Data: map[string]string{ Data: map[string]string{
"proxy-ssl-location-only": "true", "proxy-ssl-location-only": TRUE,
}, },
} }
}, },
@ -2449,7 +2455,6 @@ func TestGetBackendServers(t *testing.T) {
if len(s.Locations[0].Allowlist.CIDR) != 1 || s.Locations[0].Allowlist.CIDR[0] != "10.0.0.0/24" { if len(s.Locations[0].Allowlist.CIDR) != 1 || s.Locations[0].Allowlist.CIDR[0] != "10.0.0.0/24" {
t.Errorf("allow list was incorrectly dropped, len should be 1 and contain 10.0.0.0/24") t.Errorf("allow list was incorrectly dropped, len should be 1 and contain 10.0.0.0/24")
} }
}, },
SetConfigMap: func(ns string) *corev1.ConfigMap { SetConfigMap: func(ns string) *corev1.ConfigMap {
return &corev1.ConfigMap{ return &corev1.ConfigMap{
@ -2520,7 +2525,7 @@ func newNGINXController(t *testing.T) *NGINXController {
channels.NewRingChannel(10), channels.NewRingChannel(10),
false, false,
true, true,
&ingressclass.IngressClassConfiguration{ &ingressclass.Configuration{
Controller: "k8s.io/ingress-nginx", Controller: "k8s.io/ingress-nginx",
AnnotationValue: "nginx", AnnotationValue: "nginx",
}, },
@ -2586,7 +2591,7 @@ func newDynamicNginxController(t *testing.T, setConfigMap func(string) *corev1.C
channels.NewRingChannel(10), channels.NewRingChannel(10),
false, false,
true, true,
&ingressclass.IngressClassConfiguration{ &ingressclass.Configuration{
Controller: "k8s.io/ingress-nginx", Controller: "k8s.io/ingress-nginx",
AnnotationValue: "nginx", AnnotationValue: "nginx",
}, },

Some files were not shown because too many files have changed in this diff Show more