Fix golangci-lint errors (#10196)

* Fix golangci-lint errors

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

* Fix dupl errors

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

* Fix comments

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

* Fix errcheck lint errors

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

* Fix assert in e2e test

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

* Not interrupt the waitForPodsReady

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

* Replace string with constant

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

* Fix comments

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

* Revert write file permision

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

---------

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

View file

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

View file

@ -114,7 +114,6 @@ func main() {
fmt.Println(err)
os.Exit(1)
}
}
func backendsAll() {
@ -155,10 +154,16 @@ func backendsList() {
fmt.Println(unmarshalErr)
return
}
backends := f.([]interface{})
backends, ok := f.([]interface{})
if !ok {
fmt.Printf("unexpected type: %T", f)
}
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))
}
}
@ -180,12 +185,22 @@ func backendsGet(name string) {
fmt.Println(unmarshalErr)
return
}
backends := f.([]interface{})
backends, ok := f.([]interface{})
if !ok {
fmt.Printf("unexpected type: %T", f)
}
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 {
printed, _ := json.MarshalIndent(backend, "", " ")
printed, err := json.MarshalIndent(backend, "", " ")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(printed))
return
}
@ -213,18 +228,7 @@ func certGet(host string) {
}
func general() {
//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
}
*/
// TODO: refactor to obtain ingress-nginx pod count from the api server
var prettyBuffer bytes.Buffer
indentErr := json.Indent(&prettyBuffer, []byte("{}"), "", " ")

View file

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

View file

@ -153,7 +153,6 @@ func main() {
if errExists == nil {
conf.IsChroot = true
go logger(conf.InternalLoggerAddress)
}
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) {
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

View file

@ -47,7 +47,7 @@ func TestCreateApiserverClient(t *testing.T) {
func init() {
// the default value of nginx.TemplatePath assumes the template exists in
// 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 {
nginx.TemplatePath = path
}
@ -87,14 +87,14 @@ func TestHandleSigterm(t *testing.T) {
ingressflags.ResetForTesting(func() { t.Fatal("bad parse") })
os.Setenv("POD_NAME", podName)
os.Setenv("POD_NAMESPACE", namespace)
t.Setenv("POD_NAME", podName)
t.Setenv("POD_NAMESPACE", namespace)
oldArgs := os.Args
defer func() {
os.Setenv("POD_NAME", "")
os.Setenv("POD_NAMESPACE", "")
t.Setenv("POD_NAME", "")
t.Setenv("POD_NAMESPACE", "")
os.Args = oldArgs
}()

View file

@ -63,13 +63,14 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
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
if onlyList {
switch {
case onlyList:
command = []string{"/dbg", "backends", "list"}
} else if backend != "" {
case backend != "":
command = []string{"/dbg", "backends", "get", backend}
} else {
default:
command = []string{"/dbg", "backends", "all"}
}

View file

@ -59,7 +59,7 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
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}
pod, err := request.ChoosePod(flags, podName, deployment, selector)

View file

@ -55,7 +55,7 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
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)
if err != nil {
return err

View file

@ -55,7 +55,7 @@ type execFlags struct {
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)
if err != nil {
return err

View file

@ -47,7 +47,7 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
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)
if err != nil {
return err

View file

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

View file

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

View file

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

View file

@ -95,7 +95,7 @@ func (o *logsFlags) toStrings() []string {
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)
if err != nil {
return err

View file

@ -45,7 +45,7 @@ func CreateCommand(flags *genericclioptions.ConfigFlags) *cobra.Command {
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)
if err != nil {
return err

View file

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

View file

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

View file

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

View file

@ -24,7 +24,7 @@ import (
"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 ...
_ "k8s.io/client-go/plugin/pkg/client/auth"

View file

@ -35,7 +35,7 @@ import (
)
// ChoosePod finds a pod either by deployment or by name
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 != "" {
return GetNamedPod(flags, podName)
}
@ -54,9 +54,9 @@ func GetNamedPod(flags *genericclioptions.ConfigFlags, name string) (apiv1.Pod,
return apiv1.Pod{}, err
}
for _, pod := range allPods {
if pod.Name == name {
return pod, nil
for i := range allPods {
if allPods[i].Name == name {
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
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)
if err != nil {
return nil, err
@ -143,25 +143,26 @@ func GetNumEndpoints(flags *genericclioptions.ConfigFlags, namespace string, ser
}
ret := 0
for _, eps := range epss {
for _, ep := range eps.Endpoints {
ret += len(ep.Addresses)
for i := range epss {
eps := &epss[i]
for j := range eps.Endpoints {
ret += len(eps.Endpoints[j].Addresses)
}
}
return &ret, nil
}
// 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)
if err != nil {
return nil, err
}
var eps []discoveryv1.EndpointSlice
for _, slice := range allEndpointsSlices {
if svcName, ok := slice.ObjectMeta.GetLabels()[discoveryv1.LabelServiceName]; ok {
for i := range allEndpointsSlices {
if svcName, ok := allEndpointsSlices[i].ObjectMeta.GetLabels()[discoveryv1.LabelServiceName]; ok {
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)
}
cachedEndpointSlices = tryFilteringEndpointSlicesFromAllNamespacesCache(flags, namespace)
cachedEndpointSlices = tryFilteringEndpointSlicesFromAllNamespacesCache(namespace)
if 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[""]
if allEndpointSlices != nil {
endpointSlices := make([]discoveryv1.EndpointSlice, 0)
for _, slice := range *allEndpointSlices {
if slice.Namespace == namespace {
endpointSlices = append(endpointSlices, slice)
for i := range *allEndpointSlices {
if (*allEndpointSlices)[i].Namespace == namespace {
endpointSlices = append(endpointSlices, (*allEndpointSlices)[i])
}
}
endpointSlicesCache[namespace] = &endpointSlices
@ -242,9 +243,9 @@ func GetServiceByName(flags *genericclioptions.ConfigFlags, name string, service
services = &servicesArray
}
for _, svc := range *services {
if svc.Name == name {
return svc, nil
for i := range *services {
if (*services)[i].Name == name {
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{
LabelSelector: label,
})
if err != nil {
return make([]apiv1.Pod, 0), err
}
@ -303,9 +303,9 @@ func getDeploymentPods(flags *genericclioptions.ConfigFlags, deployment string)
}
ingressPods := make([]apiv1.Pod, 0)
for _, pod := range pods {
if util.PodInDeployment(pod, deployment) {
ingressPods = append(ingressPods, pod)
for i := range pods {
if util.PodInDeployment(&pods[i], deployment) {
ingressPods = append(ingressPods, pods[i])
}
}
@ -331,5 +331,4 @@ func getServices(flags *genericclioptions.ConfigFlags) ([]apiv1.Service, error)
}
return services.Items, nil
}

View file

@ -47,17 +47,25 @@ func PrintError(e error) {
}
// ParseVersionString returns the major, minor, and patch numbers of a version string
func ParseVersionString(v string) (int, int, int, error) {
func ParseVersionString(v string) (major, minor, patch int, err error) {
parts := versionRegex.FindStringSubmatch(v)
if len(parts) != 4 {
return 0, 0, 0, fmt.Errorf("could not parse %v as a version string (like 0.20.3)", v)
}
major, _ := strconv.Atoi(parts[1])
minor, _ := strconv.Atoi(parts[2])
patch, _ := strconv.Atoi(parts[3])
major, err = strconv.Atoi(parts[1])
if err != nil {
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
}
@ -90,7 +98,7 @@ func isVersionLessThan(a, b string) bool {
// 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
func PodInDeployment(pod apiv1.Pod, deployment string) bool {
func PodInDeployment(pod *apiv1.Pod, deployment string) bool {
for _, owner := range pod.OwnerReferences {
if owner.Controller == nil || !*owner.Controller || owner.Kind != "ReplicaSet" {
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
func GetNamespace(flags *genericclioptions.ConfigFlags) string {
namespace, _, err := flags.ToRawKubeConfigLoader().Namespace()
if err != nil || len(namespace) == 0 {
if err != nil || namespace == "" {
namespace = apiv1.NamespaceDefault
}
return namespace

View file

@ -716,7 +716,7 @@ Do not try to edit it manually.
### [[Flag] watch namespace selector](https://github.com/kubernetes/ingress-nginx/tree/main/test/e2e/settings/namespace_selector.go#L30)
- [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)

View file

@ -16,13 +16,13 @@ func hello(w http.ResponseWriter, r *http.Request) {
}
key := keys[0]
fmt.Fprintf(w, "Hello "+string(key)+"!")
fmt.Fprintf(w, "Hello "+key+"!")
}
func main() {
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 {
panic(err)
}

View file

@ -41,7 +41,7 @@ type hwServer struct {
}
// 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
}
@ -49,7 +49,7 @@ type ecServer struct {
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
}

View file

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

View file

@ -33,12 +33,12 @@ type failTestChecker struct {
t *testing.T
}
func (ftc failTestChecker) CheckIngress(ing *networking.Ingress) error {
func (ftc failTestChecker) CheckIngress(_ *networking.Ingress) error {
ftc.t.Error("checker should not be called")
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")
return nil, nil
}

View file

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

View file

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

View file

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

View file

@ -86,7 +86,6 @@ type Ingress struct {
CorsConfig cors.Config
CustomHTTPErrors []int
DefaultBackend *apiv1.Service
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
FastCGI fastcgi.Config
Denied *string
ExternalAuth authreq.Config

View file

@ -64,7 +64,11 @@ func (m mockCfg) GetService(name string) (*apiv1.Service, 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{
Secret: name,
CAFileName: "/opt/ca.pem",
@ -270,9 +274,9 @@ func TestCors(t *testing.T) {
if r.CorsAllowCredentials != foo.credentials {
t.Errorf("Returned %v but expected %v for Cors Credentials", r.CorsAllowCredentials, foo.credentials)
}
}
}
func TestCustomHTTPErrors(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress()

View file

@ -50,7 +50,7 @@ var (
)
var AuthSecretConfig = parser.AnnotationConfig{
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation,
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. `,
@ -61,20 +61,20 @@ var authSecretAnnotations = parser.Annotation{
Annotations: parser.AnnotationFields{
AuthSecretAnnotation: AuthSecretConfig,
authSecretTypeAnnotation: {
Validator: parser.ValidateRegex(*authSecretTypeRegex, true),
Validator: parser.ValidateRegex(authSecretTypeRegex, true),
Scope: parser.AnnotationScopeLocation,
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
is a user and each value is the password.`,
},
authRealmAnnotation: {
Validator: parser.ValidateRegex(*parser.CharsWithSpace, false),
Validator: parser.ValidateRegex(parser.CharsWithSpace, false),
Scope: parser.AnnotationScopeLocation,
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.`,
},
authTypeAnnotation: {
Validator: parser.ValidateRegex(*authTypeRegex, true),
Validator: parser.ValidateRegex(authTypeRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow,
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)
if err != nil {
return nil, ing_errors.LocationDenied{
return nil, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("error reading secret name from annotation: %w", err),
}
}
sns, sname, err := cache.SplitMetaNamespaceKey(s)
if err != nil {
return nil, ing_errors.LocationDenied{
return nil, ing_errors.LocationDeniedError{
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()
// We don't accept different namespaces for secrets.
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"),
}
}
@ -193,7 +193,7 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
name := fmt.Sprintf("%v/%v", sns, sname)
secret, err := a.r.GetSecret(name)
if err != nil {
return nil, ing_errors.LocationDenied{
return nil, ing_errors.LocationDeniedError{
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
}
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),
}
}
@ -238,14 +238,14 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
func dumpSecretAuthFile(filename string, secret *api.Secret) error {
val, ok := secret.Data["auth"]
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),
}
}
err := os.WriteFile(filename, val, file.ReadWriteByUser)
if err != nil {
return ing_errors.LocationDenied{
return ing_errors.LocationDeniedError{
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)
if err != nil {
return ing_errors.LocationDenied{
return ing_errors.LocationDeniedError{
Reason: fmt.Errorf("unexpected error creating password file: %w", err),
}
}

View file

@ -32,6 +32,15 @@ import (
"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 {
defaultBackend := networking.IngressBackend{
Service: &networking.IngressServiceBackend{
@ -80,7 +89,7 @@ type mockSecret struct {
}
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)
}
@ -92,7 +101,7 @@ func (m mockSecret) GetSecret(name string) (*api.Secret, error) {
return &api.Secret{
ObjectMeta: meta_v1.ObjectMeta{
Namespace: ns,
Name: "demo-secret",
Name: demoSecret,
},
Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")},
}, nil
@ -129,9 +138,9 @@ func TestIngressInvalidRealm(t *testing.T) {
ing := buildIngress()
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(AuthSecretAnnotation)] = "demo-secret"
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = demoSecret
ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t)
@ -148,14 +157,14 @@ func TestIngressInvalidDifferentNamespace(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "otherns/demo-secret"
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = othernsDemoSecret
ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir)
expected := ing_errors.LocationDenied{
expected := ing_errors.LocationDeniedError{
Reason: errors.New("cross namespace usage of secrets is not allowed"),
}
_, err := NewParser(dir, &mockSecret{}).Parse(ing)
@ -168,8 +177,8 @@ func TestIngressInvalidDifferentNamespaceAllowed(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "otherns/demo-secret"
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = othernsDemoSecret
ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t)
@ -187,14 +196,14 @@ func TestIngressInvalidSecretName(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret;xpto"
ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t)
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"),
}
_, err := NewParser(dir, &mockSecret{}).Parse(ing)
@ -207,13 +216,13 @@ func TestInvalidIngressAuthNoSecret(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t)
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"),
}
_, err := NewParser(dir, &mockSecret{}).Parse(ing)
@ -226,9 +235,9 @@ func TestIngressAuth(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret"
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "-realm-"
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = demoSecret
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = authRealm
ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t)
@ -242,10 +251,10 @@ func TestIngressAuth(t *testing.T) {
if !ok {
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)
}
if auth.Realm != "-realm-" {
if auth.Realm != authRealm {
t.Errorf("Expected -realm- as realm but returned %s", auth.Realm)
}
if !auth.Secured {
@ -257,9 +266,9 @@ func TestIngressAuthWithoutSecret(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "invalid-secret"
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "-realm-"
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = authRealm
ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t)
@ -275,10 +284,10 @@ func TestIngressAuthInvalidSecretKey(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = "basic"
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = "demo-secret"
data[parser.GetAnnotationWithPrefix(authTypeAnnotation)] = authType
data[parser.GetAnnotationWithPrefix(AuthSecretAnnotation)] = demoSecret
data[parser.GetAnnotationWithPrefix(authSecretTypeAnnotation)] = "invalid-type"
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = "-realm-"
data[parser.GetAnnotationWithPrefix(authRealmAnnotation)] = authRealm
ing.SetAnnotations(data)
_, 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()))
if err != nil {
t.Error(err)
@ -301,7 +310,10 @@ func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
t.Error(err)
}
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
}

View file

@ -57,25 +57,25 @@ var authReqAnnotations = parser.Annotation{
Group: "authentication",
Annotations: parser.AnnotationFields{
authReqURLAnnotation: {
Validator: parser.ValidateRegex(*parser.URLWithNginxVariableRegex, true),
Validator: parser.ValidateRegex(parser.URLWithNginxVariableRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskHigh,
Documentation: `This annotation allows to indicate the URL where the HTTP request should be sent`,
},
authReqMethodAnnotation: {
Validator: parser.ValidateRegex(*methodsRegex, true),
Validator: parser.ValidateRegex(methodsRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow,
Documentation: `This annotation allows to specify the HTTP method to use`,
},
authReqSigninAnnotation: {
Validator: parser.ValidateRegex(*parser.URLWithNginxVariableRegex, true),
Validator: parser.ValidateRegex(parser.URLWithNginxVariableRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskHigh,
Documentation: `This annotation allows to specify the location of the error page`,
},
authReqSigninRedirParamAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation,
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`,
@ -87,7 +87,7 @@ var authReqAnnotations = parser.Annotation{
Documentation: `This annotation allows to specify a custom snippet to use with external authentication`,
},
authReqCacheKeyAnnotation: {
Validator: parser.ValidateRegex(*parser.NGINXVariable, true),
Validator: parser.ValidateRegex(parser.NGINXVariable, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
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`,
},
authReqCacheDuration: {
Validator: parser.ValidateRegex(*parser.ExtendedCharsRegex, false),
Validator: parser.ValidateRegex(parser.ExtendedCharsRegex, false),
Scope: parser.AnnotationScopeLocation,
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`,
},
authReqResponseHeadersAnnotation: {
Validator: parser.ValidateRegex(*parser.HeadersVariable, true),
Validator: parser.ValidateRegex(parser.HeadersVariable, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation sets the headers to pass to backend once authentication request completes. They should be separated by comma.`,
},
authReqProxySetHeadersAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
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`,
},
authReqRequestRedirectAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
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 (
methodsRegex = regexp.MustCompile("(GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT|OPTIONS|TRACE)")
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
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
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
)
// ValidMethod checks is the provided string a valid HTTP method
@ -273,7 +273,7 @@ func ValidCacheDuration(duration string) bool {
seenDuration := false
for _, element := range elements {
if len(element) == 0 {
if element == "" {
continue
}
if statusCodeRegex.MatchString(element) {
@ -304,6 +304,8 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress
// 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) {
// Required Parameters
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)
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)
@ -410,7 +412,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
if err != nil && ing_errors.IsValidationError(err) {
return nil, ing_errors.NewLocationDenied("validation error")
}
if len(hstr) != 0 {
if hstr != "" {
harr := strings.Split(hstr, ",")
for _, header := range harr {
header = strings.TrimSpace(header)
@ -430,7 +432,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
cns, _, err := cache.SplitMetaNamespaceKey(proxySetHeaderMap)
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),
}
}
@ -442,7 +444,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
secCfg := a.r.GetSecurityConfiguration()
// We don't accept different namespaces for secrets.
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"),
}
}
@ -499,7 +501,7 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
// It will always return at least one duration (the default duration)
func ParseStringToCacheDurations(input string) ([]string, error) {
authCacheDuration := []string{}
if len(input) != 0 {
if input != "" {
arr := strings.Split(input, ",")
for _, duration := range arr {
duration = strings.TrimSpace(duration)

View file

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

View file

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

View file

@ -77,7 +77,10 @@ func TestAnnotation(t *testing.T) {
data[parser.GetAnnotationWithPrefix("enable-global-auth")] = "false"
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)
if !ok {
t.Errorf("expected a Config type")

View file

@ -18,11 +18,10 @@ package authtls
import (
"fmt"
"regexp"
networking "k8s.io/api/networking/v1"
"regexp"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -45,20 +44,20 @@ var (
regexChars = regexp.QuoteMeta(`()|=`)
authVerifyClientRegex = regexp.MustCompile(`on|off|optional|optional_no_ca`)
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{
Group: "authentication",
Annotations: parser.AnnotationFields{
annotationAuthTLSSecret: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation,
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`,
},
annotationAuthTLSVerifyClient: {
Validator: parser.ValidateRegex(*authVerifyClientRegex, true),
Validator: parser.ValidateRegex(authVerifyClientRegex, true),
Scope: parser.AnnotationScopeLocation,
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"`,
@ -70,7 +69,7 @@ var authTLSAnnotations = parser.Annotation{
Documentation: `This annotation defines validation depth between the provided client certificate and the Certification Authority chain.`,
},
annotationAuthTLSErrorPage: {
Validator: parser.ValidateRegex(*redirectRegex, true),
Validator: parser.ValidateRegex(redirectRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskHigh,
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"`,
},
annotationAuthTLSMatchCN: {
Validator: parser.ValidateRegex(*commonNameRegex, true),
Validator: parser.ValidateRegex(commonNameRegex, true),
Scope: parser.AnnotationScopeLocation,
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="`,
@ -130,9 +129,9 @@ func (assl1 *Config) Equal(assl2 *Config) bool {
}
// NewParser creates a new TLS authentication annotation parser
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation {
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return authTLS{
r: resolver,
r: r,
annotationConfig: authTLSAnnotations,
}
}
@ -169,7 +168,7 @@ func (a authTLS) Parse(ing *networking.Ingress) (interface{}, error) {
authCert, err := a.r.GetAuthCertificate(tlsauthsecret)
if err != nil {
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

View file

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

View file

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

View file

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

View file

@ -57,7 +57,7 @@ var CanaryAnnotations = parser.Annotation{
Documentation: `This annotation The total weight of traffic. If unspecified, it defaults to 100`,
},
canaryByHeaderAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress,
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.
@ -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`,
},
canaryByHeaderValueAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress,
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.
@ -74,7 +74,7 @@ var CanaryAnnotations = parser.Annotation{
It doesn't have any effect if the 'canary-by-header' annotation is not defined`,
},
canaryByHeaderPatternAnnotation: {
Validator: parser.ValidateRegex(*parser.IsValidRegex, false),
Validator: parser.ValidateRegex(parser.IsValidRegex, false),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
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.`,
},
canaryByCookieAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress,
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.
@ -189,7 +189,7 @@ func (c canary) GetDocumentation() parser.AnnotationFields {
return c.annotationConfig.Annotations
}
func (a canary) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (c canary) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(c.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, CanaryAnnotations.Annotations)
}

View file

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

View file

@ -31,7 +31,7 @@ var clientBodyBufferSizeConfig = parser.Annotation{
Group: "backend",
Annotations: parser.AnnotationFields{
clientBodyBufferSizeAnnotation: {
Validator: parser.ValidateRegex(*parser.SizeRegex, true),
Validator: parser.ValidateRegex(parser.SizeRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow, // Low, as it allows just a set of options
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)
}
func (a clientBodyBufferSize) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (cbbs clientBodyBufferSize) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(cbbs.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, clientBodyBufferSizeConfig.Annotations)
}

View file

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

View file

@ -29,15 +29,13 @@ const (
connectionProxyHeaderAnnotation = "connection-proxy-header"
)
var (
validConnectionHeaderValue = regexp.MustCompile(`^(close|keep-alive)$`)
)
var validConnectionHeaderValue = regexp.MustCompile(`^(close|keep-alive)$`)
var connectionHeadersAnnotations = parser.Annotation{
Group: "backend",
Annotations: parser.AnnotationFields{
connectionProxyHeaderAnnotation: {
Validator: parser.ValidateRegex(*validConnectionHeaderValue, true),
Validator: parser.ValidateRegex(validConnectionHeaderValue, true),
Scope: parser.AnnotationScopeLocation,
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"`,

View file

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

View file

@ -43,9 +43,9 @@ var (
// * Sets a group that can be (https?://)?*?.something.com:port?
// * Allows this to be repeated as much as possible, and separated by comma
// 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 = 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)
// May contain or not spaces between each verb
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`,
},
corsAllowOriginAnnotation: {
Validator: parser.ValidateRegex(*corsOriginRegexValidator, true),
Validator: parser.ValidateRegex(corsOriginRegexValidator, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
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`,
},
corsAllowHeadersAnnotation: {
Validator: parser.ValidateRegex(*parser.HeadersVariable, true),
Validator: parser.ValidateRegex(parser.HeadersVariable, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation controls which headers are accepted.
This is a multi-valued field, separated by ',' and accepts letters, numbers, _ and -`,
},
corsAllowMethodsAnnotation: {
Validator: parser.ValidateRegex(*corsMethodsRegex, true),
Validator: parser.ValidateRegex(corsMethodsRegex, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
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.`,
},
corsExposeHeadersAnnotation: {
Validator: parser.ValidateRegex(*corsExposeHeadersRegex, true),
Validator: parser.ValidateRegex(corsExposeHeadersRegex, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation controls which headers are exposed to response.
@ -260,7 +260,7 @@ func (c cors) GetDocumentation() parser.AnnotationFields {
return c.annotationConfig.Annotations
}
func (a cors) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (c cors) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(c.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, corsAnnotation.Annotations)
}

View file

@ -31,16 +31,14 @@ const (
customHTTPErrorsAnnotation = "custom-http-errors"
)
var (
// We accept anything between 400 and 599, on a comma separated.
arrayOfHTTPErrors = regexp.MustCompile(`^(?:[4,5][0-9][0-9],?)*$`)
)
// We accept anything between 400 and 599, on a comma separated.
var arrayOfHTTPErrors = regexp.MustCompile(`^(?:[4,5]\d{2},?)*$`)
var customHTTPErrorsAnnotations = parser.Annotation{
Group: "backend",
Annotations: parser.AnnotationFields{
customHTTPErrorsAnnotation: {
Validator: parser.ValidateRegex(*arrayOfHTTPErrors, true),
Validator: parser.ValidateRegex(arrayOfHTTPErrors, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow,
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, ",")
var codes []int
codes := make([]int, 0, len(cSplit))
for _, i := range cSplit {
num, err := strconv.Atoi(i)
if err != nil {
@ -88,7 +86,7 @@ func (e customhttperrors) GetDocumentation() parser.AnnotationFields {
return e.annotationConfig.Annotations
}
func (a customhttperrors) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (e customhttperrors) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(e.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, customHTTPErrorsAnnotations.Annotations)
}

View file

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

View file

@ -35,22 +35,20 @@ const (
fastCGIParamsAnnotation = "fastcgi-params-configmap"
)
var (
// fast-cgi valid parameters is just a single file name (like index.php)
regexValidIndexAnnotationAndKey = regexp.MustCompile(`^[A-Za-z0-9\.\-\_]+$`)
)
// fast-cgi valid parameters is just a single file name (like index.php)
var regexValidIndexAnnotationAndKey = regexp.MustCompile(`^[A-Za-z0-9.\-\_]+$`)
var fastCGIAnnotations = parser.Annotation{
Group: "fastcgi",
Annotations: parser.AnnotationFields{
fastCGIIndexAnnotation: {
Validator: parser.ValidateRegex(*regexValidIndexAnnotationAndKey, true),
Validator: parser.ValidateRegex(regexValidIndexAnnotationAndKey, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation can be used to specify an index file`,
},
fastCGIParamsAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
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
// rule used to indicate the fastcgiConfig.
func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
fcgiConfig := Config{}
if ing.GetAnnotations() == nil {
@ -125,7 +122,7 @@ func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
cmns, cmn, err := cache.SplitMetaNamespaceKey(cm)
if err != nil {
return fcgiConfig, ing_errors.LocationDenied{
return fcgiConfig, ing_errors.LocationDeniedError{
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)
cmap, err := a.r.GetConfigMap(cm)
if err != nil {
return fcgiConfig, ing_errors.LocationDenied{
return fcgiConfig, ing_errors.LocationDeniedError{
Reason: fmt.Errorf("unexpected error reading configmap %s: %w", cm, err),
}
}

View file

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

View file

@ -25,7 +25,6 @@ import (
"k8s.io/klog/v2"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"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/net"
@ -57,7 +56,7 @@ var globalRateLimitAnnotationConfig = parser.Annotation{
Documentation: `Configures a time window (i.e 1m) that the limit is applied`,
},
globalRateLimitKeyAnnotation: {
Validator: parser.ValidateRegex(*parser.NGINXVariable, true),
Validator: parser.ValidateRegex(parser.NGINXVariable, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskHigh,
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{}
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
}
rawWindowSize, err := parser.GetStringAnnotation(globalRateLimitWindowAnnotation, ing, a.annotationConfig.Annotations)
if err != nil && errors.IsValidationError(err) {
return config, ing_errors.LocationDenied{
if err != nil && ing_errors.IsValidationError(err) {
return config, ing_errors.LocationDeniedError{
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
}
windowSize, err := time.ParseDuration(rawWindowSize)
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),
}
}
@ -148,12 +147,12 @@ func (a globalratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
if err != nil {
klog.Warningf("invalid %s, defaulting to %s", globalRateLimitKeyAnnotation, defaultKey)
}
if len(key) == 0 {
if key == "" {
key = defaultKey
}
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
}
ignoredCIDRs, err := net.ParseCIDRs(rawIgnoredCIDRs)
@ -161,7 +160,7 @@ func (a globalratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
return nil, err
}
config.Namespace = strings.Replace(string(ing.UID), "-", "", -1)
config.Namespace = strings.ReplaceAll(string(ing.UID), "-", "")
config.Limit = limit
config.WindowSize = int(windowSize.Seconds())
config.Key = key

View file

@ -30,8 +30,10 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
const UID = "31285d47-b150-4dcf-bd6f-12c46d769f6e"
const expectedUID = "31285d47b1504dcfbd6f12c46d769f6e"
const (
UID = "31285d47-b150-4dcf-bd6f-12c46d769f6e"
expectedUID = "31285d47b1504dcfbd6f12c46d769f6e"
)
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
@ -190,10 +192,19 @@ func TestGlobalRateLimiting(t *testing.T) {
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) {
expectedJSON, _ := json.Marshal(testCase.expectedConfig)
actualJSON, _ := json.Marshal(actualConfig)
expectedJSON, err := json.Marshal(testCase.expectedConfig)
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)
}
}

View file

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

View file

@ -96,16 +96,15 @@ func (a ipallowlist) Parse(ing *networking.Ingress) (interface{}, error) {
return &SourceRange{CIDR: defaultAllowlistSourceRange}, nil
}
return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDenied{
return &SourceRange{CIDR: defaultAllowlistSourceRange}, ing_errors.LocationDeniedError{
Reason: err,
}
}
values := strings.Split(val, ",")
ipnets, ips, err := net.ParseIPNets(values...)
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),
}
}

View file

@ -93,16 +93,15 @@ func (a ipdenylist) Parse(ing *networking.Ingress) (interface{}, error) {
return &SourceRange{CIDR: defaultDenylistSourceRange}, nil
}
return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDenied{
return &SourceRange{CIDR: defaultDenylistSourceRange}, ing_errors.LocationDeniedError{
Reason: err,
}
}
values := strings.Split(val, ",")
ipnets, ips, err := net.ParseIPNets(values...)
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),
}
}

View file

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

View file

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

View file

@ -76,7 +76,10 @@ func TestIngressAccessLogConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(enableAccessLogAnnotation)] = "false"
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)
if !ok {
t.Errorf("expected a Config type")
@ -94,7 +97,10 @@ func TestIngressRewriteLogConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(enableRewriteLogAnnotation)] = "true"
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)
if !ok {
t.Errorf("expected a Config type")
@ -112,7 +118,10 @@ func TestInvalidBoolConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(enableRewriteLogAnnotation)] = "blo"
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)
if !ok {
t.Errorf("expected a Config type")

View file

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

View file

@ -27,7 +27,7 @@ import (
const (
modsecEnableAnnotation = "enable-modsecurity"
modsecEnableOwaspCoreAnnotation = "enable-owasp-core-rules"
modesecTransactionIdAnnotation = "modsecurity-transaction-id"
modesecTransactionIDAnnotation = "modsecurity-transaction-id"
modsecSnippetAnnotation = "modsecurity-snippet"
)
@ -46,8 +46,8 @@ var modsecurityAnnotation = parser.Annotation{
Risk: parser.AnnotationRiskLow,
Documentation: `This annotation enables the OWASP Core Rule Set`,
},
modesecTransactionIdAnnotation: {
Validator: parser.ValidateRegex(*parser.NGINXVariable, true),
modesecTransactionIDAnnotation: {
Validator: parser.ValidateRegex(parser.NGINXVariable, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskHigh,
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
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation {
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return modSecurity{
r: resolver,
r: r,
annotationConfig: modsecurityAnnotation,
}
}
@ -134,10 +134,10 @@ func (a modSecurity) Parse(ing *networking.Ingress) (interface{}, error) {
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 errors.IsInvalidContent(err) {
klog.Warningf("annotation %s contains invalid directive, defaulting", modesecTransactionIdAnnotation)
klog.Warningf("annotation %s contains invalid directive, defaulting", modesecTransactionIDAnnotation)
}
config.TransactionID = ""
}

View file

@ -69,8 +69,14 @@ func TestParse(t *testing.T) {
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
config := result.(*Config)
result, err := ap.Parse(ing)
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) {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}

View file

@ -51,7 +51,7 @@ var otelAnnotations = parser.Annotation{
Documentation: `This annotation enables or disables using spans from incoming requests as parent for created ones`,
},
otelOperationNameAnnotation: {
Validator: parser.ValidateRegex(*regexOperationName, true),
Validator: parser.ValidateRegex(regexOperationName, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
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
func (bd1 *Config) Equal(bd2 *Config) bool {
if bd1.Set != bd2.Set {
return false
}
@ -150,7 +149,7 @@ func (c opentelemetry) GetDocumentation() parser.AnnotationFields {
return c.annotationConfig.Annotations
}
func (a opentelemetry) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (c opentelemetry) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(c.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, otelAnnotations.Annotations)
}

View file

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

View file

@ -89,13 +89,13 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
}
}
func (s opentracing) Parse(ing *networking.Ingress) (interface{}, error) {
enabled, err := parser.GetBoolAnnotation(enableOpentracingAnnotation, ing, s.annotationConfig.Annotations)
func (o opentracing) Parse(ing *networking.Ingress) (interface{}, error) {
enabled, err := parser.GetBoolAnnotation(enableOpentracingAnnotation, ing, o.annotationConfig.Annotations)
if err != 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 {
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
}
func (s opentracing) GetDocumentation() parser.AnnotationFields {
return s.annotationConfig.Annotations
func (o opentracing) GetDocumentation() parser.AnnotationFields {
return o.annotationConfig.Annotations
}
func (a opentracing) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (o opentracing) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(o.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, opentracingAnnotations.Annotations)
}

View file

@ -26,6 +26,8 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
const enableAnnotation = "true"
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
Service: &networking.IngressServiceBackend{
@ -73,10 +75,13 @@ func TestIngressAnnotationOpentracingSetTrue(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "true"
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = enableAnnotation
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)
if !ok {
t.Errorf("expected a Config type")
@ -95,7 +100,10 @@ func TestIngressAnnotationOpentracingSetFalse(t *testing.T) {
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "false"
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)
if !ok {
t.Errorf("expected a Config type")
@ -110,11 +118,14 @@ func TestIngressAnnotationOpentracingTrustSetTrue(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = "true"
data[parser.GetAnnotationWithPrefix(opentracingTrustSpanAnnotation)] = "true"
data[parser.GetAnnotationWithPrefix(enableOpentracingAnnotation)] = enableAnnotation
data[parser.GetAnnotationWithPrefix(opentracingTrustSpanAnnotation)] = enableAnnotation
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)
if !ok {
t.Errorf("expected a Config type")
@ -136,7 +147,11 @@ func TestIngressAnnotationOpentracingUnset(t *testing.T) {
data := map[string]string{}
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)
if !ok {
t.Errorf("expected a Config type")

View file

@ -118,7 +118,7 @@ func (a ingAnnotations) parseString(name string) (string, error) {
val, ok := a[name]
if ok {
s := normalizeString(val)
if len(s) == 0 {
if s == "" {
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)
}
if parsedURL.Scheme == "" {
switch {
case parsedURL.Scheme == "":
return nil, fmt.Errorf("url scheme is empty")
} else if parsedURL.Host == "" {
case parsedURL.Host == "":
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")
}
default:
return parsedURL, nil
}
}

View file

@ -93,14 +93,16 @@ func TestGetStringAnnotation(t *testing.T) {
{"valid - A", "string", "A ", "A", false},
{"valid - B", "string", " B", "B", false},
{"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/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/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break;
`,
false},
false,
},
}
data := map[string]string{}
@ -213,8 +215,10 @@ func TestGetIntAnnotation(t *testing.T) {
func TestStringToURL(t *testing.T) {
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 {
title string
url string

View file

@ -52,7 +52,7 @@ var (
var IsValidRegex = regexp.MustCompile("^[/" + alphaNumericChars + regexEnabledChars + "]*$")
// 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:
// It is alphanumericChar + ":", "?", "&"
@ -103,7 +103,7 @@ func ValidateServerName(value string) error {
// ValidateRegex receives a regex as an argument and uses it to validate
// the value of the field.
// 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 {
if removeSpace {
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.
// 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 {
if trimSpace {
s = strings.TrimSpace(s)
@ -161,7 +161,7 @@ func ValidateDuration(value string) error {
// 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
// usage, knowing it can be critical!
func ValidateNull(value string) error {
func ValidateNull(_ string) error {
return nil
}

View file

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

View file

@ -45,9 +45,7 @@ const (
proxyMaxTempFileSizeAnnotation = "proxy-max-temp-file-size"
)
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?)+$`)
)
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?)+$`)
var proxyAnnotations = parser.Annotation{
Group: "backend",
@ -78,32 +76,32 @@ var proxyAnnotations = parser.Annotation{
By default proxy buffers number is set as 4`,
},
proxyBufferSizeAnnotation: {
Validator: parser.ValidateRegex(*parser.SizeRegex, true),
Validator: parser.ValidateRegex(parser.SizeRegex, true),
Scope: parser.AnnotationScopeLocation,
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.
By default proxy buffer size is set as "4k".`,
},
proxyCookiePathAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation,
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.`,
},
proxyCookieDomainAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation,
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.`,
},
proxyBodySizeAnnotation: {
Validator: parser.ValidateRegex(*parser.SizeRegex, true),
Validator: parser.ValidateRegex(parser.SizeRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation allows setting the maximum allowed size of a client request body.`,
},
proxyNextUpstreamAnnotation: {
Validator: parser.ValidateRegex(*validUpstreamAnnotation, false),
Validator: parser.ValidateRegex(validUpstreamAnnotation, false),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
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.`,
},
proxyRedirectFromAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation,
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`,
},
proxyRedirectToAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeLocation,
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`,
@ -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".`,
},
proxyMaxTempFileSizeAnnotation: {
Validator: parser.ValidateRegex(*parser.SizeRegex, true),
Validator: parser.ValidateRegex(parser.SizeRegex, true),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskLow,
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
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return proxy{r: r,
return proxy{
r: r,
annotationConfig: proxyAnnotations,
}
}

View file

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

View file

@ -24,7 +24,6 @@ import (
networking "k8s.io/api/networking/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"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/k8s"
@ -42,7 +41,7 @@ const (
var (
proxySSLOnOffRegex = regexp.MustCompile(`^(on|off)$`)
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 (
@ -59,7 +58,7 @@ var proxySSLAnnotation = parser.Annotation{
Group: "proxy",
Annotations: parser.AnnotationFields{
proxySSLSecretAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress,
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.
@ -68,14 +67,14 @@ var proxySSLAnnotation = parser.Annotation{
Just secrets on the same namespace of the ingress can be used.`,
},
proxySSLCiphersAnnotation: {
Validator: parser.ValidateRegex(*proxySSLCiphersRegex, true),
Validator: parser.ValidateRegex(proxySSLCiphersRegex, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
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.`,
},
proxySSLProtocolsAnnotation: {
Validator: parser.ValidateRegex(*proxySSLProtocolRegex, true),
Validator: parser.ValidateRegex(proxySSLProtocolRegex, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskLow,
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.`,
},
proxySSLVerifyAnnotation: {
Validator: parser.ValidateRegex(*proxySSLOnOffRegex, true),
Validator: parser.ValidateRegex(proxySSLOnOffRegex, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskLow,
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).`,
},
proxySSLServerNameAnnotation: {
Validator: parser.ValidateRegex(*proxySSLOnOffRegex, true),
Validator: parser.ValidateRegex(proxySSLOnOffRegex, true),
Scope: parser.AnnotationScopeIngress,
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.`,
@ -150,10 +149,11 @@ func (pssl1 *Config) Equal(pssl2 *Config) bool {
}
// NewParser creates a new TLS authentication annotation parser
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation {
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return proxySSL{
r: resolver,
annotationConfig: proxySSLAnnotation}
r: r,
annotationConfig: proxySSLAnnotation,
}
}
type proxySSL struct {
@ -208,13 +208,13 @@ func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) {
proxyCert, err := p.r.GetAuthCertificate(proxysslsecret)
if err != nil {
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.Ciphers, err = parser.GetStringAnnotation(proxySSLCiphersAnnotation, ing, p.annotationConfig.Annotations)
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)
}
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)
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)
}
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)
if err != nil {
if errors.IsValidationError(err) {
if ing_errors.IsValidationError(err) {
klog.Warningf("invalid value passed to proxy-ssl-name, defaulting to empty")
}
config.ProxySSLName = ""
@ -260,7 +260,7 @@ func (p proxySSL) GetDocumentation() parser.AnnotationFields {
return p.annotationConfig.Annotations
}
func (a proxySSL) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (p proxySSL) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(p.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, proxySSLAnnotation.Annotations)
}

View file

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

View file

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

View file

@ -54,14 +54,14 @@ var redirectAnnotations = parser.Annotation{
Documentation: `In some scenarios is required to redirect from www.domain.com to domain.com or vice versa. To enable this feature use this annotation.`,
},
temporalRedirectAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, false),
Validator: parser.ValidateRegex(parser.URLIsValidRegex, false),
Scope: parser.AnnotationScopeLocation,
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.
For example setting this annotation to https://www.google.com would redirect everything to Google with a Return Code of 302 (Moved Temporarily).`,
},
permanentRedirectAnnotation: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, false),
Validator: parser.ValidateRegex(parser.URLIsValidRegex, false),
Scope: parser.AnnotationScopeLocation,
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.
@ -174,11 +174,11 @@ func isValidURL(s string) error {
return nil
}
func (a redirect) GetDocumentation() parser.AnnotationFields {
return a.annotationConfig.Annotations
func (r redirect) GetDocumentation() parser.AnnotationFields {
return r.annotationConfig.Annotations
}
func (a redirect) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (r redirect) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(r.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, redirectAnnotations.Annotations)
}

View file

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

View file

@ -40,7 +40,7 @@ var rewriteAnnotations = parser.Annotation{
Group: "rewrite",
Annotations: parser.AnnotationFields{
rewriteTargetAnnotation: {
Validator: parser.ValidateRegex(*parser.RegexPathWithCapture, false),
Validator: parser.ValidateRegex(parser.RegexPathWithCapture, false),
Scope: parser.AnnotationScopeIngress,
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
@ -72,7 +72,7 @@ var rewriteAnnotations = parser.Annotation{
the pathType should also be defined as 'ImplementationSpecific'.`,
},
appRootAnnotation: {
Validator: parser.ValidateRegex(*parser.RegexPathWithCapture, false),
Validator: parser.ValidateRegex(parser.RegexPathWithCapture, false),
Scope: parser.AnnotationScopeLocation,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines the Application Root that the Controller must redirect if it's in / context`,

View file

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

View file

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

View file

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

View file

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

View file

@ -77,7 +77,10 @@ func TestIngressAnnotationServiceUpstreamEnabled(t *testing.T) {
data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "true"
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)
if !ok {
t.Errorf("expected a bool type")
@ -96,7 +99,10 @@ func TestIngressAnnotationServiceUpstreamSetFalse(t *testing.T) {
data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "false"
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)
if !ok {
t.Errorf("expected a bool type")
@ -110,7 +116,10 @@ func TestIngressAnnotationServiceUpstreamSetFalse(t *testing.T) {
data = map[string]string{}
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)
if !ok {
t.Errorf("expected a bool type")
@ -137,7 +146,10 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
func TestParseAnnotationsWithDefaultConfig(t *testing.T) {
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)
if !ok {
@ -158,7 +170,10 @@ func TestParseAnnotationsOverridesDefaultConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(serviceUpstreamAnnotation)] = "false"
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)
if !ok {

View file

@ -63,13 +63,15 @@ const (
// This is used to control the cookie change after request failure
annotationAffinityCookieChangeOnFailure = "session-cookie-change-on-failure"
cookieAffinity = "cookie"
)
var sessionAffinityAnnotations = parser.Annotation{
Group: "affinity",
Annotations: parser.AnnotationFields{
annotationAffinityType: {
Validator: parser.ValidateOptions([]string{"cookie"}, true, true),
Validator: parser.ValidateOptions([]string{cookieAffinity}, true, true),
Scope: parser.AnnotationScopeIngress,
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`,
@ -91,7 +93,7 @@ var sessionAffinityAnnotations = parser.Annotation{
Setting this to legacy will restore original canary behavior, when session affinity was ignored.`,
},
annotationAffinityCookieName: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
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`,
},
annotationAffinityCookieExpires: {
Validator: parser.ValidateRegex(*affinityCookieExpiresRegex, true),
Validator: parser.ValidateRegex(affinityCookieExpiresRegex, true),
Scope: parser.AnnotationScopeIngress,
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`,
},
annotationAffinityCookieMaxAge: {
Validator: parser.ValidateRegex(*affinityCookieExpiresRegex, false),
Validator: parser.ValidateRegex(affinityCookieExpiresRegex, false),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation sets the time until the cookie expires`,
},
annotationAffinityCookiePath: {
Validator: parser.ValidateRegex(*parser.URLIsValidRegex, true),
Validator: parser.ValidateRegex(parser.URLIsValidRegex, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines the Path that will be set on the cookie (required if your Ingress paths use regular expressions)`,
},
annotationAffinityCookieDomain: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeIngress,
Risk: parser.AnnotationRiskMedium,
Documentation: `This annotation defines the Domain attribute of the sticky cookie.`,
@ -149,9 +151,7 @@ var sessionAffinityAnnotations = parser.Annotation{
},
}
var (
affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
)
var affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
// Config describes the per ingress session affinity config
type Config struct {
@ -186,6 +186,11 @@ type Cookie struct {
ConditionalSameSiteNone bool `json:"conditional-samesite-none"`
}
type affinity struct {
r resolver.Resolver
annotationConfig parser.Annotation
}
// cookieAffinityParse gets the annotation values related to Cookie Affinity
// It also sets default values when no value or incorrect value is found
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
// rule used to configure the affinity directives
func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
@ -279,11 +279,10 @@ func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
}
switch at {
case "cookie":
case cookieAffinity:
cookie = a.cookieAffinityParse(ing)
default:
klog.V(3).InfoS("No default affinity found", "ingress", ing.Name)
}
return &Config{

View file

@ -83,7 +83,11 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieSecure)] = "true"
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)
if !ok {
t.Errorf("expected a Config type")

View file

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

View file

@ -31,10 +31,8 @@ const (
sslCipherAnnotation = "ssl-ciphers"
)
var (
// Should cover something like "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"
regexValidSSLCipher = regexp.MustCompile(`^[A-Za-z0-9!:+\-]*$`)
)
// Should cover something like "ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP"
var regexValidSSLCipher = regexp.MustCompile(`^[A-Za-z0-9!:+\-]*$`)
var sslCipherAnnotations = parser.Annotation{
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.`,
},
sslCipherAnnotation: {
Validator: parser.ValidateRegex(*regexValidSSLCipher, true),
Validator: parser.ValidateRegex(regexValidSSLCipher, true),
Scope: parser.AnnotationScopeIngress,
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.`,
@ -104,7 +102,7 @@ func (sc sslCipher) GetDocumentation() parser.AnnotationFields {
return sc.annotationConfig.Annotations
}
func (a sslCipher) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (sc sslCipher) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(sc.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, sslCipherAnnotations.Annotations)
}

View file

@ -42,8 +42,11 @@ func TestParse(t *testing.T) {
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: "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{annotationSSLPreferServerCiphers: "true"}, Config{"", "on"}, false},
{map[string]string{annotationSSLPreferServerCiphers: "false"}, Config{"", "off"}, false},

View file

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

View file

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

View file

@ -41,7 +41,7 @@ var upstreamHashByAnnotations = parser.Annotation{
Group: "backend",
Annotations: parser.AnnotationFields{
upstreamHashByAnnotation: {
Validator: parser.ValidateRegex(*hashByRegex, true),
Validator: parser.ValidateRegex(hashByRegex, true),
Scope: parser.AnnotationScopeLocation,
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.

View file

@ -31,7 +31,7 @@ var xForwardedForAnnotations = parser.Annotation{
Group: "backend",
Annotations: parser.AnnotationFields{
xForwardedForPrefixAnnotation: {
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
Validator: parser.ValidateRegex(parser.BasicCharsRegex, true),
Scope: parser.AnnotationScopeLocation,
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`,
@ -54,15 +54,15 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to add an x-forwarded-prefix header to the request
func (cbbs xforwardedprefix) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation(xForwardedForPrefixAnnotation, ing, cbbs.annotationConfig.Annotations)
func (x xforwardedprefix) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation(xForwardedForPrefixAnnotation, ing, x.annotationConfig.Annotations)
}
func (cbbs xforwardedprefix) GetDocumentation() parser.AnnotationFields {
return cbbs.annotationConfig.Annotations
func (x xforwardedprefix) GetDocumentation() parser.AnnotationFields {
return x.annotationConfig.Annotations
}
func (a xforwardedprefix) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
func (x xforwardedprefix) Validate(anns map[string]string) error {
maxrisk := parser.StringRiskToRisk(x.r.GetSecurityConfiguration().AnnotationsRiskLevel)
return parser.CheckAnnotationRisk(anns, maxrisk, xForwardedForAnnotations.Annotations)
}

View file

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

View file

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

View file

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

View file

@ -32,7 +32,7 @@ import (
)
func TestNginxCheck(t *testing.T) {
var tests = []struct {
tests := []struct {
healthzPath string
}{
{"/healthz"},
@ -42,7 +42,6 @@ func TestNginxCheck(t *testing.T) {
for _, tt := range tests {
testName := fmt.Sprintf("health path: %s", tt.healthzPath)
t.Run(testName, func(t *testing.T) {
mux := http.NewServeMux()
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)
}
defer listener.Close()
//nolint:gosec // Ignore not configured ReadHeaderTimeout in testing
server := &httptest.Server{
Listener: listener,
Config: &http.Server{
@ -103,10 +102,10 @@ func TestNginxCheck(t *testing.T) {
}
}()
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)
}
@ -121,7 +120,7 @@ func TestNginxCheck(t *testing.T) {
})
// pollute pid file
pidFile.Write([]byte("999999")) //nolint:errcheck
pidFile.WriteString("999999") //nolint:errcheck // Ignore the error
pidFile.Close()
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 {
req, err := http.NewRequest(http.MethodGet, healthzPath, nil)
req, err := http.NewRequest(http.MethodGet, healthzPath, http.NoBody)
if err != nil {
return fmt.Errorf("healthz error: %v", err)
}

View file

@ -29,10 +29,8 @@ import (
"k8s.io/ingress-nginx/pkg/util/runtime"
)
var (
// EnableSSLChainCompletion Autocomplete SSL certificate chains with missing intermediate CA certificates.
EnableSSLChainCompletion = false
)
// EnableSSLChainCompletion Autocomplete SSL certificate chains with missing intermediate CA certificates.
var EnableSSLChainCompletion = false
const (
// 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
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.
// 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
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
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
// 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
// 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"`
// 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
// NOTE: Deprecated
// Deprecated: HTTP2MaxHeaderSize is deprecated.
HTTP2MaxHeaderSize string `json:"http2-max-header-size,omitempty"`
// 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
// through one HTTP/2 connection, after which the next client request will lead to connection closing
// and the need of establishing a new connection.
// NOTE: Deprecated
// Deprecated: HTTP2MaxRequests is deprecated.
HTTP2MaxRequests int `json:"http2-max-requests,omitempty"`
// 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"`
// 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
// Default is X-Forwarded-For
@ -621,7 +619,7 @@ type Configuration struct {
// Default: 0.01
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
OtelSamplerParentBased bool `json:"otel-sampler-parent-based"`
@ -891,7 +889,7 @@ func NewDefault() Configuration {
EnableUnderscoresInHeaders: false,
ErrorLogLevel: errorLevel,
UseForwardedHeaders: false,
EnableRealIp: false,
EnableRealIP: false,
ForwardedForHeader: "X-Forwarded-For",
ComputeFullForwardedFor: false,
ProxyAddOriginalURIHeader: false,
@ -1039,41 +1037,41 @@ func NewDefault() Configuration {
// TemplateConfig contains the nginx configuration to render the file nginx.conf
type TemplateConfig struct {
ProxySetHeaders map[string]string
AddHeaders map[string]string
BacklogSize int
Backends []*ingress.Backend
PassthroughBackends []*ingress.SSLPassthroughBackend
Servers []*ingress.Server
TCPBackends []ingress.L4Service
UDPBackends []ingress.L4Service
HealthzURI string
Cfg Configuration
IsIPV6Enabled bool
IsSSLPassthroughEnabled bool
NginxStatusIpv4Whitelist []string
NginxStatusIpv6Whitelist []string
RedirectServers interface{}
ListenPorts *ListenPorts
PublishService *apiv1.Service
EnableMetrics bool
MaxmindEditionFiles *[]string
MonitorMaxBatchSize int
PID string
StatusPath string
StatusPort int
StreamPort int
StreamSnippets []string
ProxySetHeaders map[string]string `json:"ProxySetHeaders"`
AddHeaders map[string]string `json:"AddHeaders"`
BacklogSize int `json:"BacklogSize"`
Backends []*ingress.Backend `json:"Backends"`
PassthroughBackends []*ingress.SSLPassthroughBackend `json:"PassthroughBackends"`
Servers []*ingress.Server `json:"Servers"`
TCPBackends []ingress.L4Service `json:"TCPBackends"`
UDPBackends []ingress.L4Service `json:"UDPBackends"`
HealthzURI string `json:"HealthzURI"`
Cfg Configuration `json:"Cfg"`
IsIPV6Enabled bool `json:"IsIPV6Enabled"`
IsSSLPassthroughEnabled bool `json:"IsSSLPassthroughEnabled"`
NginxStatusIpv4Whitelist []string `json:"NginxStatusIpv4Whitelist"`
NginxStatusIpv6Whitelist []string `json:"NginxStatusIpv6Whitelist"`
RedirectServers interface{} `json:"RedirectServers"`
ListenPorts *ListenPorts `json:"ListenPorts"`
PublishService *apiv1.Service `json:"PublishService"`
EnableMetrics bool `json:"EnableMetrics"`
MaxmindEditionFiles *[]string `json:"MaxmindEditionFiles"`
MonitorMaxBatchSize int `json:"MonitorMaxBatchSize"`
PID string `json:"PID"`
StatusPath string `json:"StatusPath"`
StatusPort int `json:"StatusPort"`
StreamPort int `json:"StreamPort"`
StreamSnippets []string `json:"StreamSnippets"`
}
// ListenPorts describe the ports required to run the
// NGINX Ingress controller
type ListenPorts struct {
HTTP int
HTTPS int
Health int
Default int
SSLProxy int
HTTP int `json:"HTTP"`
HTTPS int `json:"HTTPS"`
Health int `json:"Health"`
Default int `json:"Default"`
SSLProxy int `json:"SSLProxy"`
}
// GlobalExternalAuth describe external authentication configuration for the

View file

@ -114,7 +114,7 @@ type Configuration struct {
DisableCatchAll bool
IngressClassConfiguration *ingressclass.IngressClassConfiguration
IngressClassConfiguration *ingressclass.Configuration
ValidationWebhook string
ValidationWebhookCertPath string
@ -143,7 +143,7 @@ type Configuration struct {
func getIngressPodZone(svc *apiv1.Service) string {
svcKey := k8s.MetaNamespaceKey(svc)
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 {
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
@ -154,7 +154,7 @@ func getIngressPodZone(svc *apiv1.Service) string {
}
// 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)
if err != nil {
return nil
@ -189,13 +189,16 @@ func (n *NGINXController) syncIngress(interface{}) error {
if !utilingress.IsDynamicConfigurationEnough(pcfg, n.runningConfig) {
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",
})
if err != nil {
klog.Errorf("unexpected error hashing configuration: %v", err)
}
pcfg.ConfigurationChecksum = fmt.Sprintf("%v", hash)
err := n.OnUpdate(*pcfg)
err = n.OnUpdate(*pcfg)
if err != nil {
n.metricCollector.IncReloadErrorCount()
n.metricCollector.ConfigSuccess(hash, false)
@ -263,7 +266,7 @@ func (n *NGINXController) syncIngress(interface{}) error {
func (n *NGINXController) CheckWarning(ing *networking.Ingress) ([]string, error) {
warnings := make([]string, 0)
var deprecatedAnnotations = sets.NewString()
deprecatedAnnotations := sets.NewString()
deprecatedAnnotations.Insert(
"enable-influxdb",
"influxdb-measurement",
@ -335,7 +338,7 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
}
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
cfg := n.store.GetBackendConfiguration()
@ -355,10 +358,9 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
}
for key, value := range ing.ObjectMeta.GetAnnotations() {
if parser.AnnotationsPrefix != 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)
}
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")
}
}
k8s.SetDefaultNGINXPathType(ing)
@ -401,7 +402,7 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
startTest := time.Now().UnixNano() / 1000000
_, servers, pcfg := n.getConfiguration(ings)
err = checkOverlap(ing, allIngresses, servers)
err = checkOverlap(ing, servers)
if err != nil {
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
return err
@ -452,7 +453,7 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr
return []ingress.L4Service{}
}
var svcs []ingress.L4Service
svcs := make([]ingress.L4Service, 0, len(configmap.Data))
var svcProxyProtocol ingress.ProxyProtocol
rp := []int{
@ -489,10 +490,10 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr
svcProxyProtocol.Encode = false
// Proxy Protocol is only compatible with TCP Services
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
}
if len(nsSvcPort) == 4 && strings.ToUpper(nsSvcPort[3]) == "PROXY" {
if len(nsSvcPort) == 4 && strings.EqualFold(nsSvcPort[3], "PROXY") {
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)
for i := range svc.Spec.Ports {
sp := svc.Spec.Ports[i]
//nolint:gosec // Ignore G109 error
if sp.Port == int32(targetPort) {
if sp.Protocol == proto {
endps = getEndpointsFromSlices(svc, &sp, proto, zone, n.store.GetServiceEndpointsSlices)
@ -574,7 +576,7 @@ func (n *NGINXController) getDefaultUpstream() *ingress.Backend {
}
svcKey := n.cfg.DefaultService
if len(svcKey) == 0 {
if svcKey == "" {
upstream.Endpoints = append(upstream.Endpoints, n.DefaultEndpoint())
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)
anns.StreamSnippet = ""
}
}
}
// 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,
// service name and port are the same.
//
//nolint:gocyclo // Ignore function complexity error
func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*ingress.Backend, []*ingress.Server) {
du := n.getDefaultUpstream()
upstreams := n.createUpstreams(ingresses, du)
@ -1030,7 +1033,7 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
// configure traffic shaping for canary
if anns.Canary.Enabled {
upstreams[defBackend].NoServer = true
upstreams[defBackend].TrafficShapingPolicy = newTrafficShapingPolicy(anns.Canary)
upstreams[defBackend].TrafficShapingPolicy = newTrafficShapingPolicy(&anns.Canary)
}
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
if anns.Canary.Enabled {
upstreams[name].NoServer = true
upstreams[name].TrafficShapingPolicy = newTrafficShapingPolicy(anns.Canary)
upstreams[name].TrafficShapingPolicy = newTrafficShapingPolicy(&anns.Canary)
}
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 ||
servicePort.TargetPort.String() == backendPort ||
servicePort.Name == backendPort {
endps := getEndpointsFromSlices(svc, &servicePort, apiv1.ProtocolTCP, zone, n.store.GetServiceEndpointsSlices)
if len(endps) == 0 {
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.
func (n *NGINXController) createServers(data []*ingress.Ingress,
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))
allAliases := make(map[string][]string, len(data))
@ -1282,7 +1284,8 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
Rewrite: false,
},
},
}}
},
}
// initialize all other servers
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
func nonCanaryIngressExists(ingresses []*ingress.Ingress, canaryIngresses []*ingress.Ingress) bool {
func nonCanaryIngressExists(ingresses, canaryIngresses []*ingress.Ingress) bool {
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
// 2) primary name is not the default upstream
// 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
}
// 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 {
klog.Warningf("unable to merge alternative backend %v into primary backend %v because %v is a primary backend",
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.AlternativeBackends =
append(priUps.AlternativeBackends, altUps.Name)
priUps.AlternativeBackends = append(priUps.AlternativeBackends, altUps.Name)
return true
}
@ -1574,8 +1576,8 @@ func mergeAlternativeBackend(ing *ingress.Ingress, priUps *ingress.Backend, altU
// to a backend's alternative list.
// If no match is found, then the serverless backend is deleted.
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
if ing.Spec.DefaultBackend != nil {
upsName := upstreamName(ing.Namespace, ing.Spec.DefaultBackend.Service)
@ -1585,7 +1587,6 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
if altUps == nil {
klog.Warningf("alternative backend %s has already been removed", upsName)
} else {
merged := 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
// certificate for the given host name, or an empty string.
func extractTLSSecretName(host string, ing *ingress.Ingress,
getLocalSSLCert func(string) (*ingress.SSLCert, error)) string {
getLocalSSLCert func(string) (*ingress.SSLCert, error),
) string {
if ing == nil {
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
for _, tls := range ing.Spec.TLS {
if tls.SecretName == "" {
// There's no secretName specified, so it will never be available
continue
@ -1753,6 +1753,7 @@ func externalNamePorts(name string, svc *apiv1.Service) *apiv1.ServicePort {
}
for _, svcPort := range svc.Spec.Ports {
//nolint:gosec // Ignore G109 error
if svcPort.Port != int32(port) {
continue
}
@ -1772,12 +1773,13 @@ func externalNamePorts(name string, svc *apiv1.Service) *apiv1.ServicePort {
// ExternalName without port
return &apiv1.ServicePort{
Protocol: "TCP",
//nolint:gosec // Ignore G109 error
Port: int32(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 {
if rule.HTTP == nil {
continue
@ -1870,7 +1872,7 @@ func (n *NGINXController) getStreamSnippets(ingresses []*ingress.Ingress) []stri
}
// 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{
Weight: cfg.Weight,
WeightTotal: cfg.WeightTotal,

View file

@ -60,51 +60,56 @@ import (
"k8s.io/ingress-nginx/pkg/util/file"
)
const (
exampleBackend = "example-http-svc-1-80"
TRUE = "true"
)
type fakeIngressStore struct {
ingresses []*ingress.Ingress
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
}
func (fis fakeIngressStore) GetBackendConfiguration() ngx_config.Configuration {
func (fis *fakeIngressStore) GetBackendConfiguration() ngx_config.Configuration {
return fis.configuration
}
func (fis fakeIngressStore) GetSecurityConfiguration() defaults.SecurityConfiguration {
func (fis *fakeIngressStore) GetSecurityConfiguration() defaults.SecurityConfiguration {
return defaults.SecurityConfiguration{
AnnotationsRiskLevel: fis.configuration.AnnotationsRiskLevel,
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")
}
func (fakeIngressStore) GetSecret(key string) (*corev1.Secret, error) {
func (fakeIngressStore) GetSecret(_ string) (*corev1.Secret, 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")
}
func (fakeIngressStore) GetServiceEndpointsSlices(key string) ([]*discoveryv1.EndpointSlice, error) {
func (fakeIngressStore) GetServiceEndpointsSlices(_ string) ([]*discoveryv1.EndpointSlice, error) {
return nil, fmt.Errorf("test error")
}
func (fis fakeIngressStore) ListIngresses() []*ingress.Ingress {
func (fis *fakeIngressStore) ListIngresses() []*ingress.Ingress {
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
}
func (fakeIngressStore) GetLocalSSLCert(name string) (*ingress.SSLCert, error) {
func (fakeIngressStore) GetLocalSSLCert(_ string) (*ingress.SSLCert, error) {
return nil, fmt.Errorf("test error")
}
@ -120,7 +125,7 @@ func (fakeIngressStore) GetDefaultBackend() defaults.Backend {
return defaults.Backend{}
}
func (fakeIngressStore) Run(stopCh chan struct{}) {}
func (fakeIngressStore) Run(_ chan struct{}) {}
type testNginxTestCommand struct {
t *testing.T
@ -129,7 +134,7 @@ type testNginxTestCommand struct {
err error
}
func (ntc testNginxTestCommand) ExecCommand(args ...string) *exec.Cmd {
func (ntc testNginxTestCommand) ExecCommand(_ ...string) *exec.Cmd {
return nil
}
@ -152,7 +157,7 @@ func (ntc testNginxTestCommand) Test(cfg string) ([]byte, error) {
type fakeTemplate struct{}
func (fakeTemplate) Write(conf ngx_config.TemplateConfig) ([]byte, error) {
func (fakeTemplate) Write(conf *ngx_config.TemplateConfig) ([]byte, error) {
r := []byte{}
for _, s := range conf.Servers {
if len(r) > 0 {
@ -196,7 +201,7 @@ func TestCheckIngress(t *testing.T) {
nginx.metricCollector = metric.DummyCollector{}
nginx.t = fakeTemplate{}
nginx.store = fakeIngressStore{
nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{},
}
@ -226,7 +231,7 @@ func TestCheckIngress(t *testing.T) {
}
t.Run("When the hostname is updated", func(t *testing.T) {
nginx.store = fakeIngressStore{
nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{
{
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) {
nginx.store = fakeIngressStore{
nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{},
configuration: ngx_config.Configuration{
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) {
nginx.store = fakeIngressStore{
nginx.store = &fakeIngressStore{
ingresses: []*ingress.Ingress{},
configuration: ngx_config.Configuration{
AnnotationValueWordBlocklist: "invalid_directive, another_directive",
@ -366,12 +371,11 @@ func TestCheckIngress(t *testing.T) {
}
func TestCheckWarning(t *testing.T) {
// Ensure no panic with wrong arguments
var nginx = &NGINXController{}
nginx := &NGINXController{}
nginx.t = fakeTemplate{}
nginx.store = fakeIngressStore{
nginx.store = &fakeIngressStore{
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) {
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = "true"
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = TRUE
defer func() {
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) {
rules := ing.Spec.DeepCopy().Rules
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) {
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = "true"
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("secure-verify-ca-secret")] = "true"
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("enable-influxdb")] = TRUE
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("secure-verify-ca-secret")] = TRUE
ing.ObjectMeta.Annotations[parser.GetAnnotationWithPrefix("influxdb-host")] = "blabla"
defer func() {
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) {
testCases := map[string]struct {
ingress *ingress.Ingress
@ -1537,8 +1541,8 @@ func TestExtractTLSSecretName(t *testing.T) {
}
}
//nolint:gocyclo // Ignore function complexity error
func TestGetBackendServers(t *testing.T) {
testCases := []struct {
Ingresses []*ingress.Ingress
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)
}
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'")
}
@ -2087,7 +2091,7 @@ func TestGetBackendServers(t *testing.T) {
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)
return
}
@ -2101,6 +2105,7 @@ func TestGetBackendServers(t *testing.T) {
SetConfigMap: testConfigMap,
},
{
//nolint:dupl // Ignore dupl errors for similar test case
Ingresses: []*ingress.Ingress{
{
Ingress: networking.Ingress{
@ -2208,6 +2213,7 @@ func TestGetBackendServers(t *testing.T) {
SetConfigMap: testConfigMap,
},
{
//nolint:dupl // Ignore dupl errors for similar test case
Ingresses: []*ingress.Ingress{
{
Ingress: networking.Ingress{
@ -2319,7 +2325,7 @@ func TestGetBackendServers(t *testing.T) {
SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/config", ns),
},
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),
},
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" {
t.Errorf("allow list was incorrectly dropped, len should be 1 and contain 10.0.0.0/24")
}
},
SetConfigMap: func(ns string) *corev1.ConfigMap {
return &corev1.ConfigMap{
@ -2520,7 +2525,7 @@ func newNGINXController(t *testing.T) *NGINXController {
channels.NewRingChannel(10),
false,
true,
&ingressclass.IngressClassConfiguration{
&ingressclass.Configuration{
Controller: "k8s.io/ingress-nginx",
AnnotationValue: "nginx",
},
@ -2586,7 +2591,7 @@ func newDynamicNginxController(t *testing.T, setConfigMap func(string) *corev1.C
channels.NewRingChannel(10),
false,
true,
&ingressclass.IngressClassConfiguration{
&ingressclass.Configuration{
Controller: "k8s.io/ingress-nginx",
AnnotationValue: "nginx",
},

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