Skip to content

Commit ab772d6

Browse files
committed
Support for dynamic ip ranges from urls
- Adds ipranges command to fetch ip ranges from Cloudfront and Cloudflare - Write the ipranges file on docker start - Support disabling ipv4 as well as ipv6 now - Prevent disabling both
1 parent f43e41d commit ab772d6

File tree

18 files changed

+264
-59
lines changed

18 files changed

+264
-59
lines changed

backend/Taskfile.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ tasks:
3232
silent: true
3333
- cmd: go build -buildvcs=false -ldflags="-X main.commit={{.GIT_COMMIT}} -X main.version={{.VERSION}}" -o ../dist/bin/server ./cmd/server/main.go
3434
silent: true
35+
- cmd: go build -buildvcs=false -ldflags="-X main.commit={{.GIT_COMMIT}} -X main.version={{.VERSION}}" -o ../dist/bin/ipranges ./cmd/ipranges/main.go
36+
silent: true
37+
- cmd: rm -f /etc/nginx/conf.d/include/ipranges.conf && /app/dist/bin/ipranges > /etc/nginx/conf.d/include/ipranges.conf
3538
- task: lint
3639
vars:
3740
GIT_COMMIT:

backend/cmd/ipranges/main.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"os"
10+
11+
"npm/internal/config"
12+
"npm/internal/model"
13+
14+
"github.com/rotisserie/eris"
15+
)
16+
17+
var commit string
18+
var version string
19+
var sentryDSN string
20+
21+
var cloudfrontURL = "https://ip-ranges.amazonaws.com/ip-ranges.json"
22+
var cloudflare4URL = "https://www.cloudflare.com/ips-v4"
23+
var cloudflare6URL = "https://www.cloudflare.com/ips-v6"
24+
25+
func main() {
26+
config.InitArgs(&version, &commit)
27+
if err := config.InitIPRanges(&version, &commit, &sentryDSN); err != nil {
28+
fmt.Printf("# Config ERROR: %v\n", err)
29+
os.Exit(1)
30+
}
31+
32+
exitCode := 0
33+
34+
// Cloudfront
35+
fmt.Printf("# Cloudfront Ranges from: %s\n", cloudfrontURL)
36+
if ranges, err := parseCloudfront(); err == nil {
37+
for _, item := range ranges {
38+
fmt.Printf("set_real_ip_from %s;\n", item)
39+
}
40+
} else {
41+
fmt.Printf("# ERROR: %v\n", err)
42+
}
43+
44+
// Cloudflare ipv4
45+
if !config.Configuration.DisableIPV4 {
46+
fmt.Printf("\n# Cloudflare Ranges from: %s\n", cloudflare4URL)
47+
if ranges, err := parseCloudflare(cloudflare4URL); err == nil {
48+
for _, item := range ranges {
49+
fmt.Printf("set_real_ip_from %s;\n", item)
50+
}
51+
} else {
52+
fmt.Printf("# ERROR: %v\n", err)
53+
}
54+
}
55+
56+
// Cloudflare ipv6
57+
if !config.Configuration.DisableIPV6 {
58+
fmt.Printf("\n# Cloudflare Ranges from: %s\n", cloudflare6URL)
59+
if ranges, err := parseCloudflare(cloudflare6URL); err == nil {
60+
for _, item := range ranges {
61+
fmt.Printf("set_real_ip_from %s;\n", item)
62+
}
63+
} else {
64+
fmt.Printf("# ERROR: %v\n", err)
65+
}
66+
}
67+
68+
// Done
69+
os.Exit(exitCode)
70+
}
71+
72+
func parseCloudfront() ([]string, error) {
73+
// nolint: gosec
74+
resp, err := http.Get(cloudfrontURL)
75+
if err != nil {
76+
return nil, eris.Wrapf(err, "Failed to download Cloudfront IP Ranges from %s", cloudfrontURL)
77+
}
78+
79+
// nolint: errcheck, gosec
80+
defer resp.Body.Close()
81+
body, err := io.ReadAll(resp.Body)
82+
if err != nil {
83+
return nil, eris.Wrapf(err, "Failed to read Cloudfront IP Ranges body")
84+
}
85+
86+
var result model.CloudfrontIPRanges
87+
if err := json.Unmarshal(body, &result); err != nil {
88+
return nil, eris.Wrapf(err, "Failed to unmarshal Cloudfront IP Ranges file")
89+
}
90+
91+
ranges := make([]string, 0)
92+
if !config.Configuration.DisableIPV4 {
93+
for _, item := range result.IPV4Prefixes {
94+
ranges = append(ranges, item.Value)
95+
}
96+
}
97+
if !config.Configuration.DisableIPV6 {
98+
for _, item := range result.IPV6Prefixes {
99+
ranges = append(ranges, item.Value)
100+
}
101+
}
102+
103+
return ranges, nil
104+
}
105+
106+
func parseCloudflare(url string) ([]string, error) {
107+
// nolint: gosec
108+
resp, err := http.Get(url)
109+
if err != nil {
110+
return nil, eris.Wrapf(err, "Failed to download Cloudflare IP Ranges from %s", url)
111+
}
112+
113+
// nolint: errcheck, gosec
114+
defer resp.Body.Close()
115+
116+
scanner := bufio.NewScanner(resp.Body)
117+
scanner.Split(bufio.ScanLines)
118+
119+
ranges := make([]string, 0)
120+
for scanner.Scan() {
121+
if scanner.Text() != "" {
122+
ranges = append(ranges, scanner.Text())
123+
}
124+
}
125+
return ranges, nil
126+
}

backend/internal/config/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ func Init(version, commit, sentryDSN *string) {
2929
loadKeys()
3030
}
3131

32+
// InitIPRanges will initialise the config for the ipranges command
33+
func InitIPRanges(version, commit, sentryDSN *string) error {
34+
ErrorReporting = true
35+
Version = *version
36+
Commit = *commit
37+
err := envconfig.InitWithPrefix(&Configuration, "NPM")
38+
initLogger(*sentryDSN)
39+
return err
40+
}
41+
3242
// Init initialises the Log object and return it
3343
func initLogger(sentryDSN string) {
3444
// this removes timestamp prefixes from logs

backend/internal/config/vars.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ type acmesh struct {
3838

3939
// Configuration is the main configuration object
4040
var Configuration struct {
41-
DataFolder string `json:"data_folder" envconfig:"optional,default=/data"`
42-
Acmesh acmesh `json:"acmesh"`
43-
Log log `json:"log"`
41+
DataFolder string `json:"data_folder" envconfig:"optional,default=/data"`
42+
DisableIPV4 bool `json:"disable_ipv4" envconfig:"optional"`
43+
DisableIPV6 bool `json:"disable_ipv6" envconfig:"optional"`
44+
Acmesh acmesh `json:"acmesh"`
45+
Log log `json:"log"`
4446
}
4547

4648
// GetWellknown returns the well known path
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package model
2+
3+
// CloudfrontIPRangePrefix is used within config for cloudfront
4+
type CloudfrontIPRangeV4Prefix struct {
5+
Value string `json:"ip_prefix"`
6+
}
7+
8+
// CloudfrontIPRangeV6Prefix is used within config for cloudfront
9+
type CloudfrontIPRangeV6Prefix struct {
10+
Value string `json:"ipv6_prefix"`
11+
}
12+
13+
// CloudfrontIPRanges is the main config for cloudfront
14+
type CloudfrontIPRanges struct {
15+
IPV4Prefixes []CloudfrontIPRangeV4Prefix `json:"prefixes"`
16+
IPV6Prefixes []CloudfrontIPRangeV6Prefix `json:"ipv6_prefixes"`
17+
}

backend/internal/nginx/control.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ func ConfigureHost(h host.Model) error {
3939
Certificate: certificateTemplate,
4040
ConfDir: fmt.Sprintf("%s/nginx/hosts", config.Configuration.DataFolder),
4141
Config: Config{ // todo
42-
Ipv4: true,
43-
Ipv6: false,
42+
Ipv4: !config.Configuration.DisableIPV4,
43+
Ipv6: !config.Configuration.DisableIPV6,
4444
},
4545
DataDir: config.Configuration.DataFolder,
4646
Host: h.GetTemplate(),

docker/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ RUN mkdir -p /dist \
3636
FROM jc21/nginx-full:acmesh AS final
3737

3838
COPY --from=gobuild /dist/server /app/bin/server
39+
COPY --from=gobuild /dist/ipranges /app/bin/ipranges
3940
# these certs are used for testing in CI
4041
COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem
4142
COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt

docker/docker-compose.ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ services:
77
environment:
88
- NPM_LOG_LEVEL=debug
99
- NPM_LOG_FORMAT=json
10-
- DISABLE_IPV6=true
10+
- NPM_DISABLE_IPV6=true
1111
volumes:
1212
- '/etc/localtime:/etc/localtime:ro'
1313
- npm_data_ci:/data

docker/rootfs/bin/common.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ log_info () {
3131
echo -e "${BLUE}${CYAN}$1${RESET}"
3232
}
3333

34+
log_warn () {
35+
echo -e "${BLUE}${YELLOW}WARNING: $1${RESET}"
36+
}
37+
3438
log_error () {
3539
echo -e "${RED}$1${RESET}"
3640
}
@@ -52,7 +56,8 @@ get_group_id () {
5256

5357
# param $1: value
5458
is_true () {
55-
if [ "$1" == 'true' ] || [ "$1" == 'on' ] || [ "$1" == '1' ] || [ "$1" == 'yes' ]; then
59+
VAL=$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')
60+
if [ "$VAL" == 'true' ] || [ "$VAL" == 'on' ] || [ "$VAL" == '1' ] || [ "$VAL" == 'yes' ]; then
5661
echo '1'
5762
else
5863
echo '0'

docker/rootfs/etc/nginx/conf.d/include/ip_ranges.conf

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)