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:
parent
46d87d3462
commit
b3060bfbd0
253 changed files with 2434 additions and 2113 deletions
|
@ -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"
|
||||||
|
|
|
@ -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("{}"), "", " ")
|
||||||
|
|
|
@ -47,5 +47,4 @@ func logger(address string) {
|
||||||
|
|
||||||
server.Wait()
|
server.Wait()
|
||||||
klog.Infof("Stopping logger")
|
klog.Infof("Stopping logger")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -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"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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, ",")))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"`,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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'`,
|
||||||
|
|
|
@ -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 = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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`,
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue