Skip to content

Commit

Permalink
Merge branch 'main' into metrics
Browse files Browse the repository at this point in the history
# Conflicts:
#	CONTRIBUTORS.md
#	README.md
#	go.mod
#	go.sum
  • Loading branch information
cromefire committed Oct 25, 2024
2 parents 632e86d + 42e1457 commit e64a109
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 55 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
Contains work of the following people besides me:

* [Adrian Rudnik](https://github.com/adrianrudnik)
* [Benedikt Ritter](https://github.com/britter)
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ENV FRITZBOX_ENDPOINT_URL="http://fritz.box:49000" \
DYNDNS_SERVER_USERNAME="" \
DYNDNS_SERVER_PASSWORD="" \
CLOUDFLARE_API_EMAIL="" \
CLOUDFLARE_API_KEY="" \
CLOUDFLARE_API_KEY_FILE="" \
CLOUDFLARE_ZONES_IPV4="" \
CLOUDFLARE_ZONES_IPV6="" \
DEVICE_LOCAL_ADDRESS_IPV6=""
Expand Down
76 changes: 57 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,22 @@ You can use this strategy if you have:

In your `.env` file or your system environment variables you can be configured:

| Variable name | Description |
|------------------------|------------------------------------------------------|
| DYNDNS_SERVER_BIND | required, network interface to bind to, i.e. `:8080` |
| DYNDNS_SERVER_USERNAME | optional, username for the DynDNS service |
| DYNDNS_SERVER_PASSWORD | optional, password for the DynDNS service |
| Variable name | Description |
|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| DYNDNS_SERVER_BIND | required, network interface to bind to, i.e. `:8080`. |
| DYNDNS_SERVER_USERNAME | optional, username for the DynDNS service. |
| DYNDNS_SERVER_PASSWORD | optional, password for the DynDNS service. |
| DYNDNS_SERVER_PASSWORD_FILE | optional, path to a file containing the password for the DynDNS service. It's recommended to use this over `DYNDNS_SERVER_PASSWORD`. |

Now configure the FRITZ!Box router to push IP changes towards this service. Log into the admin panel and go to
`Internet > Shares > DynDNS tab` and setup a `Custom` provider:

| Property | Description / Value |
|------------|---------------------------------------------------------------------------------------|
| Update-URL | http://[server-ip]/ip?v4=\<ipaddr\>&v6=\<ip6addr\>&prefix=\<ip6lanprefix\> |
| Domain | Enter at least one domain name so the router can probe if the update was successfully |
| Username | Enter '_' if `DYNDNS_SERVER_USERNAME` env is unset |
| Password | Enter '_' if `DYNDNS_SERVER_PASSWORD` env is unset |
| Property | Description / Value |
|------------|----------------------------------------------------------------------------------------|
| Update-URL | http://[server-ip]/ip?v4=\<ipaddr\>&v6=\<ip6addr\>&prefix=\<ip6lanprefix\> |
| Domain | Enter at least one domain name so the router can probe if the update was successfully. |
| Username | Enter '_' if `DYNDNS_SERVER_USERNAME` is unset. |
| Password | Enter '_' if `DYNDNS_SERVER_PASSWORD` and `DYNDNS_SERVER_PASSWORD_FILE` are unset. |

If you specified credentials you need to append them as additional GET parameters into the Update-URL
like `&username=<username>&password=<pass>`.
Expand All @@ -73,7 +74,7 @@ In your `.env` file or your system environment variables you can be configured:
|----------------------------|--------------------------------------------------------------------------------------------------------|
| FRITZBOX_ENDPOINT_URL | optional, how can we reach the router, i.e. `http://fritz.box:49000`, the port should be 49000 anyway. |
| FRITZBOX_ENDPOINT_TIMEOUT | optional, a duration we give the router to respond, i.e. `10s`. |
| FRITZBOX_ENDPOINT_INTERVAL | optional, a duration how often we want to poll the WAN IPs from the router, i.e. `120s` |
| FRITZBOX_ENDPOINT_INTERVAL | optional, a duration how often we want to poll the WAN IPs from the router, i.e. `120s`. |

You can try the endpoint URL in the browser to make sure you have the correct port, you should receive
an `404 ERR_NOT_FOUND`.
Expand All @@ -90,13 +91,15 @@ to the config, you won't be able to see it again.

In your `.env` file or your system environment variables you can be configured:

| Variable name | Description |
|-----------------------|-------------------------------------------------------------------|
| CLOUDFLARE_API_TOKEN | required, your Cloudflare API Token |
| CLOUDFLARE_ZONES_IPV4 | comma-separated list of domains to update with new IPv4 addresses |
| CLOUDFLARE_ZONES_IPV6 | comma-separated list of domains to update with new IPv6 addresses |
| CLOUDFLARE_API_EMAIL | deprecated, your Cloudflare account email |
| CLOUDFLARE_API_KEY | deprecated, your Cloudflare Global API key |
| Variable name | Description |
|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| CLOUDFLARE_API_TOKEN | required if `CLOUDFLARE_API_TOKEN_FILE` is unset, your Cloudflare API Token. |
| CLOUDFLARE_API_TOKEN_FILE | required if `CLOUDFLARE_API_TOKEN` is unset, path to a file containing your Cloudflare API Token. It's recommended to use this over `CLOUDFLARE_API_TOKEN`. |
| CLOUDFLARE_ZONES_IPV4 | comma-separated list of domains to update with new IPv4 addresses. |
| CLOUDFLARE_ZONES_IPV6 | comma-separated list of domains to update with new IPv6 addresses. |
| CLOUDFLARE_API_EMAIL | deprecated, your Cloudflare account email. |
| CLOUDFLARE_API_KEY | deprecated, your Cloudflare Global API key. |
| CLOUDFLARE_API_KEY_FILE | deprecated, path to a file containing your Cloudflare Global API key. |

This service allows to update multiple records, an advanced example would be:

Expand Down Expand Up @@ -153,6 +156,41 @@ Now we could configure the FRITZ!Box
to `http://[docker-host-ip]:49000/ip?v4=<ipaddr>&v6=<ip6addr>&prefix=<ip6lanprefix>` and it should trigger the update
process.

## Passing secrets

As shown above, secrets can be passed via environment variables.
If passing secrets via environment variables does not work for your use case, it's also possible to pass them via the filesystem.
In order to pass a secret via a file, append `_FILE` to the respective environment variable name and configure it to point to the file containing the secret.
For example in order to pass the Cloudflare API token via a file, configure an environment variable with name `CLOUDFLARE_API_TOKEN_FILE` with the absolute path to a file containing the secret.

Here is an example `docker-compose.yml` passing the file `cloudflare_api_key.txt` from the host to the docker container using docker compose secrets:

```
version: '3.7'
services:
updater:
image: ghcr.io/cromefire/fritzbox-cloudflare-dyndns:1
network_mode: host
environment:
- DYNDNS_SERVER_BIND=:8080
- CLOUDFLARE_API_TOKEN_FILE=/run/secrets/cloudflare_api_token
- DYNDNS_SERVER_PASSWORD_FILE=/run/secrets/fb_server_password
- CLOUDFLARE_ZONES_IPV4=test.example.com
- CLOUDFLARE_ZONES_IPV6=test.example.com
secrets:
- cloudflare_api_token
- fb_server_password
secrets:
cloudflare_api_token:
file: ./cloudflare_api_token.txt
fb_server_password:
file: ./fb_server_password.txt
```

See https://docs.docker.com/compose/how-tos/use-secrets/ for more information about docker compose secrets.

## Docker build

A pre-built docker image is also available on this
Expand Down
2 changes: 1 addition & 1 deletion alpine.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ENV FRITZBOX_ENDPOINT_URL="http://fritz.box:49000" \
DYNDNS_SERVER_USERNAME="" \
DYNDNS_SERVER_PASSWORD="" \
CLOUDFLARE_API_EMAIL="" \
CLOUDFLARE_API_KEY="" \
CLOUDFLARE_API_KEY_FILE="" \
CLOUDFLARE_ZONES_IPV4="" \
CLOUDFLARE_ZONES_IPV6="" \
DEVICE_LOCAL_ADDRESS_IPV6=""
Expand Down
10 changes: 0 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,9 @@ require (
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)
21 changes: 0 additions & 21 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/cloudflare-go v0.108.0 h1:C4Skfjd8I8X3uEOGmQUT4/iGyZcWdkIU7HwvMoLkEE0=
github.com/cloudflare/cloudflare-go v0.108.0/go.mod h1:m492eNahT/9MsN7Ppnoge8AaI7QhVFtEgVm3I9HJFeU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand All @@ -12,45 +8,28 @@ github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc h1:LMEBgNcZUqXaP7evD1PZcL6EcDVa2QOFuI+cqM3+AJM=
Expand Down
26 changes: 23 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ func newUpdater(logger *slog.Logger) (*cloudflare.Updater, []*util.UpdateStatus)
logger = logger.With(util.SubsystemAttr(subsystem))
u := cloudflare.NewUpdater(slog.Default().With(util.SubsystemAttr(subsystem)), subsystem)

token := os.Getenv("CLOUDFLARE_API_TOKEN")
token := readSecret("CLOUDFLARE_API_TOKEN")
email := os.Getenv("CLOUDFLARE_API_EMAIL")
key := os.Getenv("CLOUDFLARE_API_KEY")
key := readSecret("CLOUDFLARE_API_KEY")

if token == "" {
if email == "" || key == "" {
Expand Down Expand Up @@ -140,7 +140,7 @@ func startPushServer(out chan<- *net.IP, localIp *net.IP, logger *slog.Logger, c

server := dyndns.NewServer(out, localIp, logger, subsystem, &status)
server.Username = os.Getenv("DYNDNS_SERVER_USERNAME")
server.Password = os.Getenv("DYNDNS_SERVER_PASSWORD")
server.Password = readSecret("DYNDNS_SERVER_PASSWORD")

pushMux := http.NewServeMux()

Expand Down Expand Up @@ -218,3 +218,23 @@ func startMetricsServer(bind string, logger *slog.Logger, status util.Status, to

logger.Info("metrics server started", slog.String("addr", bind))
}

func readSecret(envName string) string {
secret := os.Getenv(envName)

if secret != "" {
slog.Info("Secret passed via environment variable " + envName + ". It's recommended to pass secrets via files, see https://github.com/cromefire/fritzbox-cloudflare-dyndns?tab=readme-ov-file#passing-secrets.")
return secret
}

passwordFilePath := os.Getenv(envName + "_FILE")
if passwordFilePath != "" {
content, err := os.ReadFile(passwordFilePath)
if err != nil {
slog.Error("Failed to read secret from file "+passwordFilePath, logging.ErrorAttr(err))
} else {
secret = strings.TrimSuffix(strings.TrimSuffix(string(content), "\r\n"), "\n")
}
}
return secret
}

0 comments on commit e64a109

Please sign in to comment.