Merge pull request #8 from levelsoftware/enable-use-of-environment-variables-for-docker

Enable use of environment variables for docker
This commit is contained in:
Jacob Haug 2023-10-09 09:23:56 -04:00 committed by GitHub
commit 712e2166d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 236 additions and 83 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,28 @@ 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_TRUSTED_HEADERS="X-Real-IP,X-Forwaded-For"
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,38 @@ 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)
} else {
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, &config)
if err != nil {
panic(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 +73,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 +102,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)
}
}

138
config/config.go Normal file
View File

@ -0,0 +1,138 @@
package config
import (
"os"
"strconv"
"strings"
)
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", ""),
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
trustedHeaders := getenv_string("ECHOIP_TRUSTED_HEADERS", "")
defaultConfig.TrustedHeaders = strings.Split(trustedHeaders, ",")
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=