feat: add domain lookup endpoint
This commit is contained in:
22
README.md
22
README.md
@@ -60,6 +60,28 @@ Client
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Domain-Lookup (optional)
|
||||||
|
|
||||||
|
Für die Validierung von Institutions-Domains kann ein Lookup genutzt werden:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /lookup?domain=uni-stuttgart.de
|
||||||
|
```
|
||||||
|
|
||||||
|
Antwort (JSON):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"domain": "uni-stuttgart.de",
|
||||||
|
"nren": true,
|
||||||
|
"asn": 12345,
|
||||||
|
"asn_org": "Universitaet Stuttgart",
|
||||||
|
"ips": ["129.69.1.1"],
|
||||||
|
"matched_ip": "129.69.1.1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Integration
|
## Integration
|
||||||
|
|
||||||
Der Service wird als **Traefik ForwardAuth Middleware** eingebunden.
|
Der Service wird als **Traefik ForwardAuth Middleware** eingebunden.
|
||||||
|
|||||||
@@ -60,6 +60,28 @@ Client
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Domain-Lookup (optional)
|
||||||
|
|
||||||
|
Für Backend-Validierung von Institutions-Domains:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /lookup?domain=uni-stuttgart.de
|
||||||
|
```
|
||||||
|
|
||||||
|
Antwort (JSON):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"domain": "uni-stuttgart.de",
|
||||||
|
"nren": true,
|
||||||
|
"asn": 12345,
|
||||||
|
"asn_org": "Universitaet Stuttgart",
|
||||||
|
"ips": ["129.69.1.1"],
|
||||||
|
"matched_ip": "129.69.1.1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Integration
|
## Integration
|
||||||
|
|
||||||
Der Service wird als **Traefik ForwardAuth Middleware** eingebunden.
|
Der Service wird als **Traefik ForwardAuth Middleware** eingebunden.
|
||||||
|
|||||||
89
main.go
89
main.go
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -19,6 +20,16 @@ type asnRecord struct {
|
|||||||
Org string `maxminddb:"autonomous_system_organization"`
|
Org string `maxminddb:"autonomous_system_organization"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type lookupResponse struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
NREN bool `json:"nren"`
|
||||||
|
ASN *uint `json:"asn,omitempty"`
|
||||||
|
ASNOrg string `json:"asn_org,omitempty"`
|
||||||
|
IPs []string `json:"ips"`
|
||||||
|
MatchedIP string `json:"matched_ip,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
db *maxminddb.Reader
|
db *maxminddb.Reader
|
||||||
nrenASNs map[uint]struct{}
|
nrenASNs map[uint]struct{}
|
||||||
@@ -76,6 +87,12 @@ func remoteIP(r *http.Request) string {
|
|||||||
return r.RemoteAddr
|
return r.RemoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeJSON(w http.ResponseWriter, status int, payload any) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
_ = json.NewEncoder(w).Encode(payload)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *server) authHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *server) authHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !s.ready.Load() {
|
if !s.ready.Load() {
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
@@ -116,6 +133,77 @@ func (s *server) authHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *server) lookupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.ready.Load() {
|
||||||
|
writeJSON(w, http.StatusServiceUnavailable, lookupResponse{
|
||||||
|
NREN: false,
|
||||||
|
Error: "service not ready",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := strings.TrimSpace(r.URL.Query().Get("domain"))
|
||||||
|
if domain == "" {
|
||||||
|
writeJSON(w, http.StatusBadRequest, lookupResponse{
|
||||||
|
NREN: false,
|
||||||
|
Error: "missing domain",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ips, err := net.LookupIP(domain)
|
||||||
|
if err != nil || len(ips) == 0 {
|
||||||
|
writeJSON(w, http.StatusOK, lookupResponse{
|
||||||
|
Domain: domain,
|
||||||
|
NREN: false,
|
||||||
|
Error: "domain lookup failed",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := lookupResponse{
|
||||||
|
Domain: domain,
|
||||||
|
NREN: false,
|
||||||
|
IPs: make([]string, 0, len(ips)),
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstASN *uint
|
||||||
|
var firstOrg string
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
ipStr := ip.String()
|
||||||
|
resp.IPs = append(resp.IPs, ipStr)
|
||||||
|
|
||||||
|
var rec asnRecord
|
||||||
|
if err := s.db.Lookup(ip, &rec); err != nil || rec.ASN == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstASN == nil {
|
||||||
|
firstASN = new(uint)
|
||||||
|
*firstASN = rec.ASN
|
||||||
|
firstOrg = rec.Org
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := s.nrenASNs[rec.ASN]; ok {
|
||||||
|
asn := rec.ASN
|
||||||
|
resp.NREN = true
|
||||||
|
resp.ASN = &asn
|
||||||
|
resp.ASNOrg = rec.Org
|
||||||
|
resp.MatchedIP = ipStr
|
||||||
|
writeJSON(w, http.StatusOK, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstASN != nil {
|
||||||
|
resp.ASN = firstASN
|
||||||
|
resp.ASNOrg = firstOrg
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJSON(w, http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
mmdbPath := getenv("MMDB_PATH", "/data/GeoLite2-ASN.mmdb")
|
mmdbPath := getenv("MMDB_PATH", "/data/GeoLite2-ASN.mmdb")
|
||||||
asnListPath := getenv("ASN_LIST_PATH", "/data/nren_asns.txt")
|
asnListPath := getenv("ASN_LIST_PATH", "/data/nren_asns.txt")
|
||||||
@@ -146,6 +234,7 @@ func main() {
|
|||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/auth", s.authHandler)
|
mux.HandleFunc("/auth", s.authHandler)
|
||||||
|
mux.HandleFunc("/lookup", s.lookupHandler)
|
||||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
|
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
|
||||||
if s.asnCount < s.minASN {
|
if s.asnCount < s.minASN {
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
|||||||
Reference in New Issue
Block a user