Skip to content

Commit 833948e

Browse files
committed
Add CLI option -nginx-status-allow-cidrs to allow easily restricting access to sensitive endpoints via CLI argument.
1 parent 8cbc907 commit 833948e

File tree

9 files changed

+144
-22
lines changed

9 files changed

+144
-22
lines changed

cmd/nginx-ingress/main.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"os"
1010
"os/signal"
11+
"strings"
1112
"syscall"
1213
"time"
1314

@@ -83,6 +84,8 @@ The external address of the service is used when reporting the status of Ingress
8384
leaderElectionEnabled = flag.Bool("enable-leader-election", false,
8485
"Enable Leader election to avoid multiple replicas of the controller reporting the status of Ingress resources -- only one replica will report status. See -report-ingress-status flag.")
8586

87+
nginxStatusAllowCIDRs = flag.String("nginx-status-allow-cidrs", "127.0.0.1", `Whitelist IPv4 IP/CIDR blocks to allow access to NGINX stub_status or the NGINX Plus API. Separate multiple IP/CIDR by commas.`)
88+
8689
nginxStatusPort = flag.Int("nginx-status-port", 8080,
8790
"Set the port where the NGINX stub_status or the NGINX Plus API is exposed. [1023 - 65535]")
8891

@@ -104,9 +107,14 @@ func main() {
104107
glog.Fatalf("Invalid value for nginx-status-port: %v", portValidationError)
105108
}
106109

110+
var err error
111+
allowedCIDRs, err := parseNginxStatusAllowCIDRs(*nginxStatusAllowCIDRs)
112+
if err != nil {
113+
glog.Fatalf(`Invalid value for nginx-status-allow-cidrs: %v`, err)
114+
}
115+
107116
glog.Infof("Starting NGINX Ingress controller Version=%v GitCommit=%v\n", version, gitCommit)
108117

109-
var err error
110118
var config *rest.Config
111119
if *proxyURL != "" {
112120
config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
@@ -146,7 +154,7 @@ func main() {
146154
nginxIngressTemplatePath = *ingressTemplatePath
147155
}
148156

149-
templateExecutor, err := nginx.NewTemplateExecutor(nginxConfTemplatePath, nginxIngressTemplatePath, *healthStatus, *nginxStatus, *nginxStatusPort)
157+
templateExecutor, err := nginx.NewTemplateExecutor(nginxConfTemplatePath, nginxIngressTemplatePath, *healthStatus, *nginxStatus, allowedCIDRs, *nginxStatusPort)
150158
if err != nil {
151159
glog.Fatalf("Error creating TemplateExecutor: %v", err)
152160
}
@@ -335,3 +343,35 @@ func validateStatusPort(nginxStatusPort int) error {
335343
}
336344
return nil
337345
}
346+
347+
// parseNginxStatusAllowCIDRs converts a comma separated CIDR/IP address string into an array of CIDR/IP addresses.
348+
// It returns an array of the valid CIDR/IP addresses or an error if given an invalid address.
349+
func parseNginxStatusAllowCIDRs(input string) (cidrs []string, err error) {
350+
cidrsArray := strings.Split(input, ",")
351+
for _, cidr := range cidrsArray {
352+
trimmedCidr := strings.TrimSpace(cidr)
353+
err := validateCIDRorIP(trimmedCidr)
354+
if err != nil {
355+
return cidrs, err
356+
}
357+
cidrs = append(cidrs, trimmedCidr)
358+
}
359+
return cidrs, nil
360+
}
361+
362+
// validateCIDRorIP makes sure a given string is either a valid CIDR block or IP address.
363+
// It an error if it is not valid.
364+
func validateCIDRorIP(cidr string) error {
365+
if cidr == "" {
366+
return fmt.Errorf("invalid CIDR address: an empty string is an invalid CIDR block or IP address")
367+
}
368+
_, _, err := net.ParseCIDR(cidr)
369+
if err == nil {
370+
return nil
371+
}
372+
ip := net.ParseIP(cidr)
373+
if ip == nil {
374+
return fmt.Errorf("invalid IP address: %v", cidr)
375+
}
376+
return nil
377+
}

cmd/nginx-ingress/main_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"errors"
45
"testing"
56
)
67

@@ -22,3 +23,77 @@ func TestValidateStatusPort(t *testing.T) {
2223
}
2324

2425
}
26+
27+
func TestParseNginxStatusAllowCIDRs(t *testing.T) {
28+
29+
var tests = []struct {
30+
input string
31+
expected []string
32+
expectedError error
33+
}{
34+
{"earth, ,,furball",
35+
[]string{},
36+
errors.New("invalid IP address: earth")},
37+
{"127.0.0.1",
38+
[]string{"127.0.0.1"},
39+
nil},
40+
{"10.0.1.0/24",
41+
[]string{"10.0.1.0/24"},
42+
nil},
43+
{"127.0.0.1,10.0.1.0/24,68.121.233.214 , 24.24.24.24/32",
44+
[]string{"127.0.0.1", "10.0.1.0/24", "68.121.233.214", "24.24.24.24/32"}, nil},
45+
{"127.0.0.1,10.0.1.0/24, ,,furball",
46+
[]string{"127.0.0.1", "10.0.1.0/24"},
47+
errors.New("invalid CIDR address: an empty string is an invalid CIDR block or IP address")},
48+
{"false",
49+
[]string{},
50+
errors.New("invalid IP address: false")},
51+
}
52+
53+
for _, test := range tests {
54+
splitArray, err := parseNginxStatusAllowCIDRs(test.input)
55+
if err != test.expectedError {
56+
if test.expectedError == nil {
57+
t.Errorf("parseNginxStatusAllowCIDRs(%q) returned an error %q when it should not have returned an error.", test.input, err.Error())
58+
} else if err == nil {
59+
t.Errorf("parseNginxStatusAllowCIDRs(%q) returned no error when it should have returned error %q", test.input, test.expectedError)
60+
} else if err.Error() != test.expectedError.Error() {
61+
t.Errorf("parseNginxStatusAllowCIDRs(%q) returned error %q when it should have returned error %q", test.input, err.Error(), test.expectedError)
62+
}
63+
}
64+
65+
for _, expectedEntry := range test.expected {
66+
if !contains(splitArray, expectedEntry) {
67+
t.Errorf("parseNginxStatusAllowCIDRs(%q) did not include %q but returned %q", test.input, expectedEntry, splitArray)
68+
}
69+
}
70+
}
71+
72+
}
73+
74+
func TestValidateCIDRorIP(t *testing.T) {
75+
badCIDRs := []string{"localhost", "thing", "~", "!!!", "", " ", "-1"}
76+
for _, badCIDR := range badCIDRs {
77+
err := validateCIDRorIP(badCIDR)
78+
if err == nil {
79+
t.Errorf(`Expected error for invalid CIDR "%v"\n`, badCIDR)
80+
}
81+
}
82+
83+
goodCIDRs := []string{"0.0.0.0/32", "0.0.0.0/0", "127.0.0.1/32", "127.0.0.0/24", "23.232.65.42"}
84+
for _, goodCIDR := range goodCIDRs {
85+
err := validateCIDRorIP(goodCIDR)
86+
if err != nil {
87+
t.Errorf("Error for valid CIDR: %v err: %v\n", goodCIDR, err)
88+
}
89+
}
90+
}
91+
92+
func contains(s []string, e string) bool {
93+
for _, a := range s {
94+
if a == e {
95+
return true
96+
}
97+
}
98+
return false
99+
}

docs/cli-arguments.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ Usage of ./nginx-ingress:
4141
Enable support for NGINX Plus
4242
-nginx-status
4343
Enable the NGINX stub_status, or the NGINX Plus API. (default true)
44+
-nginx-status-allow-cidrs
45+
Whitelist IPv4 IP/CIDR blocks to allow access to NGINX stub_status or the NGINX Plus API.
46+
Separate multiple IP/CIDR by commas.
4447
-nginx-status-port int
4548
Set the port where the NGINX stub_status or the NGINX Plus API is exposed. [1023 - 65535] (default 8080)
4649
-proxy string

internal/controller/controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ func TestFindIngressesForSecret(t *testing.T) {
926926
t.Run(test.desc, func(t *testing.T) {
927927
fakeClient := fake.NewSimpleClientset()
928928

929-
templateExecutor, err := nginx.NewTemplateExecutor("../nginx/templates/nginx-plus.tmpl", "../nginx/templates/nginx-plus.ingress.tmpl", true, true, 8080)
929+
templateExecutor, err := nginx.NewTemplateExecutor("../nginx/templates/nginx-plus.tmpl", "../nginx/templates/nginx-plus.ingress.tmpl", true, true, []string{"127.0.0.1"}, 8080)
930930
if err != nil {
931931
t.Fatalf("templateExecuter could not start: %v", err)
932932
}

internal/nginx/configurator_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ func createExpectedConfigForMergeableCafeIngress() IngressNginxConfig {
524524
}
525525

526526
func createTestConfigurator() (*Configurator, error) {
527-
templateExecutor, err := NewTemplateExecutor("templates/nginx-plus.tmpl", "templates/nginx-plus.ingress.tmpl", true, true, 8080)
527+
templateExecutor, err := NewTemplateExecutor("templates/nginx-plus.tmpl", "templates/nginx-plus.ingress.tmpl", true, true, []string{"127.0.0.1"}, 8080)
528528
if err != nil {
529529
return nil, err
530530
}
@@ -537,7 +537,7 @@ func createTestConfigurator() (*Configurator, error) {
537537
}
538538

539539
func createTestConfiguratorInvalidIngressTemplate() (*Configurator, error) {
540-
templateExecutor, err := NewTemplateExecutor("templates/nginx-plus.tmpl", "templates/nginx-plus.ingress.tmpl", true, true, 8080)
540+
templateExecutor, err := NewTemplateExecutor("templates/nginx-plus.tmpl", "templates/nginx-plus.ingress.tmpl", true, true, []string{"127.0.0.1"}, 8080)
541541
if err != nil {
542542
return nil, err
543543
}

internal/nginx/nginx.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ type MainConfig struct {
154154
StreamLogFormat string
155155
HealthStatus bool
156156
NginxStatus bool
157+
NginxStatusAllowCIDRs []string
157158
NginxStatusPort int
158159
MainSnippets []string
159160
HTTPSnippets []string

internal/nginx/template_executor.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import (
88

99
// TemplateExecutor executes NGINX configuration templates
1010
type TemplateExecutor struct {
11-
HealthStatus bool
12-
NginxStatus bool
13-
NginxStatusPort int
14-
mainTemplate *template.Template
15-
ingressTemplate *template.Template
11+
HealthStatus bool
12+
NginxStatus bool
13+
NginxStatusAllowCIDRs []string
14+
NginxStatusPort int
15+
mainTemplate *template.Template
16+
ingressTemplate *template.Template
1617
}
1718

1819
// NewTemplateExecutor creates a TemplateExecutor
19-
func NewTemplateExecutor(mainTemplatePath string, ingressTemplatePath string, healthStatus bool, nginxStatus bool, nginxStatusPort int) (*TemplateExecutor, error) {
20+
func NewTemplateExecutor(mainTemplatePath string, ingressTemplatePath string, healthStatus bool, nginxStatus bool, nginxStatusAllowCIDRs []string, nginxStatusPort int) (*TemplateExecutor, error) {
2021
// template name must be the base name of the template file https://golang.org/pkg/text/template/#Template.ParseFiles
2122
nginxTemplate, err := template.New(path.Base(mainTemplatePath)).ParseFiles(mainTemplatePath)
2223
if err != nil {
@@ -29,11 +30,12 @@ func NewTemplateExecutor(mainTemplatePath string, ingressTemplatePath string, he
2930
}
3031

3132
return &TemplateExecutor{
32-
mainTemplate: nginxTemplate,
33-
ingressTemplate: ingressTemplate,
34-
HealthStatus: healthStatus,
35-
NginxStatus: nginxStatus,
36-
NginxStatusPort: nginxStatusPort,
33+
mainTemplate: nginxTemplate,
34+
ingressTemplate: ingressTemplate,
35+
HealthStatus: healthStatus,
36+
NginxStatus: nginxStatus,
37+
NginxStatusAllowCIDRs: nginxStatusAllowCIDRs,
38+
NginxStatusPort: nginxStatusPort,
3739
}, nil
3840
}
3941

@@ -63,6 +65,7 @@ func (te *TemplateExecutor) UpdateIngressTemplate(templateString *string) error
6365
func (te *TemplateExecutor) ExecuteMainConfigTemplate(cfg *MainConfig) ([]byte, error) {
6466
cfg.HealthStatus = te.HealthStatus
6567
cfg.NginxStatus = te.NginxStatus
68+
cfg.NginxStatusAllowCIDRs = te.NginxStatusAllowCIDRs
6669
cfg.NginxStatusPort = te.NginxStatusPort
6770

6871
var configBuffer bytes.Buffer

internal/nginx/templates/nginx-plus.tmpl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ http {
9696

9797
location = /dashboard.html {
9898
}
99-
100-
allow 127.0.0.1;
99+
{{ range $value := .NginxStatusAllowCIDRs }}{{ if ne $value "" }}
100+
allow {{$value}};{{ end }}
101+
{{end}}
101102
deny all;
102-
103103
location /api {
104104
api write=off;
105105
}

internal/nginx/templates/nginx.tmpl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ http {
8686
# stub_status
8787
server {
8888
listen {{.NginxStatusPort}};
89-
90-
allow 127.0.0.1;
89+
{{ range $value := .NginxStatusAllowCIDRs }}{{ if ne $value "" }}
90+
allow {{$value}};{{ end }}
91+
{{end}}
9192
deny all;
92-
9393
location /stub_status {
9494
stub_status;
9595
}

0 commit comments

Comments
 (0)