Enable use of Environment Variables

I didn't want to kep the API Key in a file because it could be exposed
so I thought a environment variable would be good. Might of got carried
away and didn't need to put all config options in environment variables.

commit ba35fa73512a5d0d868c9e9147f55fe50db0ec99
Author: Ethan Knowlton <ethan@dubtown.dev>
Date:   Sat Oct 7 22:35:31 2023 -0400

    removed debug statement

commit 3808cc26dfac70e5c205df807b26855bf4562196
Author: Ethan Knowlton <ethan@dubtown.dev>
Date:   Sat Oct 7 22:32:27 2023 -0400

    Could Cause Problems

    Shouldn't need to run make as super user, but usually
    you would run `make install`. I could add this to the install step
    and remove the install from all step but it could mess up publishing
    to ip.level.io.

    Just going to remove it for now.

commit aa0cb39b08e71e1ef897f2d3f3a4fc7e5aff4c03
Author: Ethan Knowlton <ethan@dubtown.dev>
Date:   Sat Oct 7 20:05:14 2023 -0400

    adding env variables to readme
This commit is contained in:
Ethan Knowlton 2023-10-08 14:46:51 -04:00
parent 5c5503085e
commit 967931fd25
11 changed files with 233 additions and 82 deletions

View File

@ -1,2 +1,3 @@
Dockerfile
Dockerfile.geoip
/etc/systemd
Dockerfile.geoip

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
.vscode/
.idea/
/bin/
.envrc

View File

@ -1,6 +1,6 @@
# Build
FROM golang:1.15-buster AS build
WORKDIR /go/src/github.com/mpolden/echoip
FROM golang:1.18-buster AS build
WORKDIR /go/src/github.com/levelsoftware/echoip
COPY . .
# Must build without cgo because libc is unavailable in runtime image
@ -8,7 +8,7 @@ ENV GO111MODULE=on CGO_ENABLED=0
RUN make
# Run
FROM scratch
FROM alpine
EXPOSE 8080
COPY --from=build /go/bin/echoip /opt/echoip/

View File

@ -1,5 +1,5 @@
DOCKER ?= docker
DOCKER_IMAGE ?= mpolden/echoip
DOCKER_IMAGE ?= levelsoftware/echoip
OS := $(shell uname)
ifeq ($(OS),Linux)
TAR_OPTS := --wildcards
@ -22,11 +22,11 @@ check-fmt:
lint: check-fmt vet
install: install-config
install:
go install ./...
install-config:
sudo install -D etc/echoip/config.toml /etc/echoip/config.toml
install -D etc/echoip/config.toml /etc/echoip/config.toml
databases := GeoLite2-City GeoLite2-Country GeoLite2-ASN
@ -51,7 +51,7 @@ docker-multiarch-builder:
$(DOCKER) run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker-build:
$(DOCKER) build -t $(DOCKER_IMAGE) .
$(DOCKER) build -t $(DOCKER_IMAGE) .
docker-login:
@echo "$(DOCKER_PASSWORD)" | $(DOCKER) login -u "$(DOCKER_USERNAME)" --password-stdin

View File

@ -132,6 +132,27 @@ CityFile = ""
AsnFile = ""
```
### Environment Variables for Configuration
You can also use environment variables for configuration, most likely used for Docker.
```
ECHOIP_LISTEN=":8080"
ECHOIP_TEMPLATE_DIR="html/"
ECHOIP_REDIS_URL="redis://localhost:6379"
ECHOIP_DATABASE="ipstack"
ECHOIP_IPSTACK_API_KEY="askdfj39sjdkf29dsjfk39sdfkj3"
ECHOIP_GEOIP_COUNTRY_FILE="/full/path/to/file.db"
ECHOIP_GEOIP_CITY_FILE="/full/path/to/file.db"
ECHOIP_GEOIP_ASN_FILE="/full/path/to/file.db"
ECHOIP_CACHE_TTL=3600
ECHOIP_REVERSE_LOOKUP=true
ECHOIP_PORT_LOOKUP=true
ECHOIP_SHOW_SPONSOR=true
ECHOIP_PROFILE=false
ECHOIP_IPSTACK_USE_HTTPS=true
ECHOIP_IPSTACK_ENABLE_SECURITY=true
```
### Caching with Redis
You can connect EchoIP to a Redis client to cache each request per IP. You can configure the life of the key in `config.CacheTtl`.

View File

@ -9,6 +9,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/levelsoftware/echoip/cache"
"github.com/levelsoftware/echoip/config"
"github.com/levelsoftware/echoip/http"
"github.com/levelsoftware/echoip/iputil"
"github.com/levelsoftware/echoip/iputil/geo"
@ -33,61 +34,40 @@ func init() {
log.SetFlags(log.Lshortfile)
}
type IPStack struct {
ApiKey string
UseHttps bool
EnableSecurity bool
}
type GeoIP struct {
CountryFile string
CityFile string
AsnFile string
}
type Config struct {
Listen string
TemplateDir string
RedisUrl string
CacheTtl int
ReverseLookup bool
PortLookup bool
ShowSponsor bool
TrustedHeaders []string
Database string
Profile bool
IPStack IPStack
GeoIP GeoIP
}
func main() {
file, err := os.Open("/etc/echoip/config.toml")
runConfig, err := config.GetConfig()
if err != nil {
panic(err)
log.Fatalf("Error building configuration: %s", err)
}
file, err := os.Open("/etc/echoip/config.toml")
defer file.Close()
var config Config
b, err := io.ReadAll(file)
if err != nil {
panic(err)
log.Printf("Error opening config file (/etc/echoip/config.toml): %s", err)
}
err = toml.Unmarshal(b, &config)
if err != nil {
panic(err)
if err == nil {
var b []byte
b, err = io.ReadAll(file)
if err != nil {
log.Printf("Error reading config file (/etc/echoip/config.toml): %s", err)
}
err = toml.Unmarshal(b, &runConfig)
if err != nil {
log.Fatalf("Error parsing config file: %s", err)
}
}
var parser parser.Parser
if config.Database == "geoip" {
if runConfig.Database == "geoip" {
log.Print("Using GeoIP for IP database")
geo, err := geo.Open(
config.GeoIP.CountryFile,
config.GeoIP.CityFile,
config.GeoIP.AsnFile,
runConfig.GeoIP.CountryFile,
runConfig.GeoIP.CityFile,
runConfig.GeoIP.AsnFile,
)
if err != nil {
log.Fatal(err)
@ -95,27 +75,27 @@ func main() {
parser = &geo
}
if config.Database == "ipstack" {
log.Print("Using GeoIP for IP database")
if config.IPStack.EnableSecurity {
log.Print("Enable Security Module ( Requires Professional Plus account )")
if runConfig.Database == "ipstack" {
log.Print("Using IP Stack for IP database")
if runConfig.IPStack.EnableSecurity {
log.Print("Enable IP Stack Security Module ( Requires Professional Plus account )")
}
enableSecurity := ipstackApi.ParamEnableSecurity(config.IPStack.EnableSecurity)
apiKey := ipstackApi.ParamToken(config.IPStack.ApiKey)
useHttps := ipstackApi.ParamUseHTTPS(config.IPStack.UseHttps)
if config.IPStack.UseHttps {
enableSecurity := ipstackApi.ParamEnableSecurity(runConfig.IPStack.EnableSecurity)
apiKey := ipstackApi.ParamToken(runConfig.IPStack.ApiKey)
useHttps := ipstackApi.ParamUseHTTPS(runConfig.IPStack.UseHttps)
if runConfig.IPStack.UseHttps {
log.Print("Use IP Stack HTTPS API ( Requires non-free account )")
}
if err := ipstackApi.Init(apiKey, enableSecurity, useHttps); err != nil {
log.Fatal(err)
log.Fatalf("Error initializing IP Stack client: %s", err)
}
ips := ipstack.IPStack{}
parser = &ips
}
var serverCache cache.Cache
if len(config.RedisUrl) > 0 {
redisCache, err := cache.RedisCache(config.RedisUrl)
if len(runConfig.RedisUrl) > 0 {
redisCache, err := cache.RedisCache(runConfig.RedisUrl)
serverCache = &redisCache
if err != nil {
log.Fatal(err)
@ -124,34 +104,35 @@ func main() {
serverCache = &cache.Null{}
}
server := http.New(parser, serverCache, config.CacheTtl, config.Profile)
server.IPHeaders = config.TrustedHeaders
server := http.New(parser, serverCache, runConfig.CacheTtl, runConfig.Profile)
server.IPHeaders = runConfig.TrustedHeaders
if _, err := os.Stat(config.TemplateDir); err == nil {
server.Template = config.TemplateDir
if _, err := os.Stat(runConfig.TemplateDir); err == nil {
server.Template = runConfig.TemplateDir
} else {
log.Printf("Not configuring default handler: Template not found: %s", config.TemplateDir)
log.Printf("Not configuring default handler: Template not found: %s", runConfig.TemplateDir)
}
if config.ReverseLookup {
if runConfig.ReverseLookup {
log.Println("Enabling reverse lookup")
server.LookupAddr = iputil.LookupAddr
}
if config.PortLookup {
if runConfig.PortLookup {
log.Println("Enabling port lookup")
server.LookupPort = iputil.LookupPort
}
if config.ShowSponsor {
if runConfig.ShowSponsor {
log.Println("Enabling sponsor logo")
server.Sponsor = config.ShowSponsor
server.Sponsor = runConfig.ShowSponsor
}
if len(config.TrustedHeaders) > 0 {
log.Printf("Trusting remote IP from header(s): %s", config.TrustedHeaders)
if len(runConfig.TrustedHeaders) > 0 {
log.Printf("Trusting remote IP from header(s): %s", runConfig.TrustedHeaders)
}
if config.Profile {
if runConfig.Profile {
log.Printf("Enabling profiling handlers")
}
log.Printf("Listening on http://%s", config.Listen)
if err := server.ListenAndServe(config.Listen); err != nil {
log.Printf("Listening on http://%s", runConfig.Listen)
if err := server.ListenAndServe(runConfig.Listen); err != nil {
log.Fatal(err)
}
}

135
config/config.go Normal file
View File

@ -0,0 +1,135 @@
package config
import (
"os"
"strconv"
)
type IPStack struct {
ApiKey string
UseHttps bool
EnableSecurity bool
}
type GeoIP struct {
CountryFile string
CityFile string
AsnFile string
}
type Config struct {
Listen string
TemplateDir string
RedisUrl string
CacheTtl int
ReverseLookup bool
PortLookup bool
ShowSponsor bool
TrustedHeaders []string
Database string
Profile bool
IPStack IPStack
GeoIP GeoIP
}
func GetConfig() (Config, error) {
defaultConfig := Config{
Listen: getenv_string("ECHOIP_LISTEN", ":8080"),
TemplateDir: getenv_string("ECHOIP_TEMPLATE_DIR", "html/"),
RedisUrl: getenv_string("ECHOIP_REDIS_URL", ""),
TrustedHeaders: nil,
Database: getenv_string("ECHOIP_DATABASE", "geoip"),
IPStack: IPStack{
ApiKey: getenv_string("ECHOIP_IPSTACK_API_KEY", ""),
},
GeoIP: GeoIP{
CountryFile: getenv_string("ECHOIP_GEOIP_COUNTRY_FILE", ""),
CityFile: getenv_string("ECHOIP_GEOIP_CITY_FILE", ""),
AsnFile: getenv_string("ECHOIP_GEOIP_ASN_FILE", ""),
},
}
cacheTtl, err := getenv_int("ECHOIP_CACHE_TTL", 3600)
if err != nil {
return Config{}, err
}
defaultConfig.CacheTtl = cacheTtl
reverseLookup, err := getenv_bool("ECHOIP_REVERSE_LOOKUP", false)
if err != nil {
return Config{}, err
}
defaultConfig.ReverseLookup = reverseLookup
portLookup, err := getenv_bool("ECHOIP_PORT_LOOKUP", false)
if err != nil {
return Config{}, err
}
defaultConfig.PortLookup = portLookup
showSponsor, err := getenv_bool("ECHOIP_SHOW_SPONSOR", false)
if err != nil {
return Config{}, err
}
defaultConfig.ShowSponsor = showSponsor
profile, err := getenv_bool("ECHOIP_PROFILE", false)
if err != nil {
return Config{}, err
}
defaultConfig.Profile = profile
ipStackUseHttps, err := getenv_bool("ECHOIP_IPSTACK_USE_HTTPS", false)
if err != nil {
return Config{}, err
}
defaultConfig.IPStack.UseHttps = ipStackUseHttps
ipStackEnableSecurity, err := getenv_bool("ECHOIP_IPSTACK_ENABLE_SECURITY", false)
if err != nil {
return Config{}, err
}
defaultConfig.IPStack.EnableSecurity = ipStackEnableSecurity
return defaultConfig, nil
}
func getenv_int(key string, fallback int) (int, error) {
value := os.Getenv(key)
if len(value) > 0 {
intValue, err := strconv.Atoi(value)
if err != nil {
return 0, err
}
return intValue, nil
}
return fallback, nil
}
func getenv_string(key string, fallback string) string {
value := os.Getenv(key)
if len(value) == 0 {
return fallback
}
return value
}
func getenv_bool(key string, fallback bool) (bool, error) {
value := os.Getenv(key)
if len(value) > 0 {
boolValue, err := strconv.ParseBool(value)
if err != nil {
return false, err
}
return boolValue, nil
}
return fallback, nil
}

View File

@ -1,6 +1,14 @@
version: '3.8'
services:
echoip:
image: hub.01a.in/echoip:latest
environment:
- ECHOIP_DATABASE="ipstack"
- ECHOIP_IPSTACK_API_KEY="ipstack"
ports:
- '8080:8080'
cache:
image: redis:6.2-alpine
restart: always

View File

@ -1,5 +1,5 @@
Listen = ":8080"
TemplateDir = "html/index.html"
TemplateDir = "html"
RedisUrl = "redis://localhost:6379"
CacheTtl = 3600 # in seconds
ReverseLookup = true

5
go.mod
View File

@ -18,6 +18,7 @@ require (
github.com/vmihailenco/go-tinylfu v0.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.11.0 // indirect
)

11
go.sum
View File

@ -128,15 +128,17 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -160,8 +162,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -174,8 +177,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=