This commit is contained in:
2021-07-21 15:38:38 +02:00
parent 64c307cb7b
commit 588c818c0d
9 changed files with 87 additions and 48 deletions

View File

@@ -1,12 +1,14 @@
package main package main
import ( import (
"crypto/subtle"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gorilla/mux" "github.com/gorilla/mux"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net" "net"
"net/http" "net/http"
auth "github.com/abbot/go-http-auth"
) )
@@ -108,11 +110,11 @@ func (h *HttpServer) doaction(w http.ResponseWriter, r *http.Request, params *Ad
case "add": case "add":
host := r.URL.Query().Get("host") host := r.URL.Query().Get("host")
_, err := h._database.CreateHost(host) pw, _, err := h._database.CreateHost(host)
if err == nil { if err == nil {
params.Alerts = append(params.Alerts, Alert{ params.Alerts = append(params.Alerts, Alert{
Type: "success", Type: "success",
Message: fmt.Sprintf("created host %s", host), Message: fmt.Sprintf("created host %s\npassword: %s", host, pw),
}) })
} else { } else {
params.Alerts = append(params.Alerts, Alert{ params.Alerts = append(params.Alerts, Alert{
@@ -154,6 +156,7 @@ func (h *HttpServer) adminPage(w http.ResponseWriter, r *http.Request) {
params.Alerts = make([]Alert, 0) params.Alerts = make([]Alert, 0)
params.Hosts = make(map[string]string) params.Hosts = make(map[string]string)
params.IpAddresses = h._ipAddresses params.IpAddresses = h._ipAddresses
params.LogoutUrl = h._oauth.LogoutUrl
h.doaction(w,r, params) h.doaction(w,r, params)
@@ -161,8 +164,7 @@ func (h *HttpServer) adminPage(w http.ResponseWriter, r *http.Request) {
claims, _ := h._oauth.GetClaims(w, token) claims, _ := h._oauth.GetClaims(w, token)
params.Email = claims.Email params.Claims = *claims
params.Profile = claims.Profile
if settings.FilterString != nil { if settings.FilterString != nil {
params.Hosts = h._database.GetExistingHosts(*settings.FilterString) params.Hosts = h._database.GetExistingHosts(*settings.FilterString)
@@ -275,13 +277,35 @@ func (h *HttpServer) updateHandler(w http.ResponseWriter, r *http.Request) {
log.Info("authorization successful.") log.Info("authorization successful.")
log.Info("will update host ", host, " ip ", myip) log.Info("will update host ", host, " ip ", myip)
if !h._database.ExistHost(host) {
w.WriteHeader(http.StatusInternalServerError)
return
} else {
err := h._dnsclient.Update(host, myip)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
} else {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
}
}
func (h *HttpServer) Listen() { func (h *HttpServer) Listen() {
h._server.ListenAndServe() h._server.ListenAndServe()
} }
func Secret(user, realm string) string {
if user == "john" {
// password is "hello"
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
}
return ""
}
func CreateHttpServer(config *Config) *HttpServer { func CreateHttpServer(config *Config) *HttpServer {
var httpserver *HttpServer var httpserver *HttpServer
@@ -315,6 +339,10 @@ func CreateHttpServer(config *Config) *HttpServer {
Handler: r, Handler: r,
} }
auth := auth.NewBasicAuthenticator(config.AuthRealm, func(user string) {
return httpserver._database._db.Get("hosts/"+ user)
})
r.HandleFunc("/register", httpserver.registerHandler) r.HandleFunc("/register", httpserver.registerHandler)
r.HandleFunc("/update", httpserver.updateHandler) r.HandleFunc("/update", httpserver.updateHandler)
r.HandleFunc("/admin", httpserver.adminPage) r.HandleFunc("/admin", httpserver.adminPage)

View File

@@ -10,5 +10,6 @@ type Config struct {
OAuth2ClientSecret string OAuth2ClientSecret string
IssuerUrl string IssuerUrl string
ZoneUrl string ZoneUrl string
AuthRealm string
} }

View File

@@ -1,23 +1,19 @@
package main package main
import ( import (
"bytes"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"crypto/sha512"
"encoding/base64"
"errors" "errors"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/patrickmn/go-cache"
password "github.com/sethvargo/go-password/password"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util" "github.com/syndtr/goleveldb/leveldb/util"
"math" bcrypt "golang.org/x/crypto/bcrypt"
"math/big"
"os" "os"
"path" "path"
"strings" "strings"
"time" "time"
"github.com/patrickmn/go-cache"
) )
type Database struct { type Database struct {
@@ -57,11 +53,11 @@ func (d *Database) Authorize(host string, token_user string) bool {
return false return false
} }
if len(token) < sha512.Size { if len(token) < sha256.Size {
return false return false
} }
if len(token_user) < sha512.Size { if len(token_user) < sha256.Size {
return false return false
} }
@@ -74,24 +70,21 @@ func (d *Database) Authorize(host string, token_user string) bool {
return false return false
} }
func generateToken() string { func generateToken() (string, []byte, error) {
maxInt := big.NewInt(math.MaxInt64) pw, err := password.Generate(64, 10, 10, false, false)
randInt, err := rand.Int(rand.Reader, maxInt)
if err != nil { if err != nil {
log.Fatal(err) return "", nil, err
} }
h_plain := []byte(randInt.String() + time.Now().String())
h:= sha256.Sum256(h_plain) hash, errHash := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
for i:=0;i<10000; i++ { if errHash != nil {
h = sha256.Sum256(h[:]) return "", nil , errHash
} }
var buf bytes.Buffer
base64.NewEncoder(base64.URLEncoding, &buf).Write(h[:]) return pw, hash, nil
return buf.String()
} }
func (d *Database) IsBannedHost(host string) bool { func (d *Database) IsBannedHost(host string) bool {
@@ -119,24 +112,27 @@ func (d *Database) IncrementBanHost(host string) {
} }
func (d *Database) CreateHost(host string) (string, error) { func (d *Database) CreateHost(host string) (string, []byte, error) {
if d.ExistHost(host) { if d.ExistHost(host) {
return "", errors.New("host already existent") return "", nil, errors.New("host already existent")
} }
if host == "" { if host == "" {
return "", errors.New("given hostname is empty") return "", nil, errors.New("given hostname is empty")
} }
if _, ok := dns.IsDomainName(host + "." + d._zone); !ok { if _, ok := dns.IsDomainName(host + "." + d._zone); !ok {
return "", errors.New("given hostname is invalid") return "", nil, errors.New("given hostname is invalid")
} }
token := generateToken() pw, hash, errToken := generateToken()
d._db.Put([]byte("hosts/" + host), []byte(token), nil) if errToken != nil {
return "", nil, errToken
}
return token, nil d._db.Put([]byte("hosts/" + host), hash, nil)
return pw, hash, nil
} }

3
go.mod
View File

@@ -3,11 +3,14 @@ module ddnsService
go 1.15 go 1.15
require ( require (
github.com/abbot/go-http-auth v0.4.0
github.com/coreos/go-oidc/v3 v3.0.0 github.com/coreos/go-oidc/v3 v3.0.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/miekg/dns v1.1.35 github.com/miekg/dns v1.1.35
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/sethvargo/go-password v0.2.0
github.com/sirupsen/logrus v1.7.0 github.com/sirupsen/logrus v1.7.0
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
) )

4
go.sum
View File

@@ -1,4 +1,6 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0=
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ= github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ=
github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -27,6 +29,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@@ -4,9 +4,9 @@ func main() {
config := Config{ config := Config{
DataDir: "/var/lib/ddnsService", DataDir: "/var/lib/ddnsService",
Addr: ":8999", OAuth2ClientID: "0oa472q93Zu227hXY5d6", Addr: ":8999", OAuth2ClientID: "ddnsService",
OAuth2ClientSecret: "sj98SdfZxjkYxUd8NCbyhSuPX1D7kPARDRJ0kVUe", OAuth2ClientSecret: "",
IssuerUrl: "https://dev-1614211.okta.com/oauth2/default", IssuerUrl: "https://auth.voglfrei.net/auth/realms/master",
OAuth2RedirectUrl: "http://localhost:8999/admin", OAuth2RedirectUrl: "http://localhost:8999/admin",
ZoneUrl: "hub.voglfrei.net"} ZoneUrl: "hub.voglfrei.net"}
server := CreateHttpServer(&config) server := CreateHttpServer(&config)

View File

@@ -13,15 +13,19 @@ import (
type OidcClams struct { type OidcClams struct {
Email string `json:"email"` Email string `json:"email"`
Profile string `json:"profile"` Profile string `json:"profile"`
Username string `json:"preferred_username"`
} }
type OAuth2 struct { type OAuth2 struct {
LogoutUrl string `json:"end_session_endpoint"`
_ctx context.Context _ctx context.Context
_oauth2Config *oauth2.Config _oauth2Config *oauth2.Config
_oidcVerifier *oidc.IDTokenVerifier _oidcVerifier *oidc.IDTokenVerifier
_oidcProvider *oidc.Provider _oidcProvider *oidc.Provider
_nonces map[string]string _nonces map[string]string
} }
func (o *OAuth2) GetClaims(w http.ResponseWriter, token *oidc.IDToken) (*OidcClams, error) { func (o *OAuth2) GetClaims(w http.ResponseWriter, token *oidc.IDToken) (*OidcClams, error) {
@@ -42,6 +46,7 @@ func (o *OAuth2) checkOAuth(w http.ResponseWriter, r *http.Request, allowRedirec
if err == nil { if err == nil {
log.Info("got id_token cookie") log.Info("got id_token cookie")
token, err := o._oidcVerifier.Verify(o._ctx, cookie.Value) token, err := o._oidcVerifier.Verify(o._ctx, cookie.Value)
if err != nil { if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError) http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return nil, false return nil, false
@@ -60,7 +65,6 @@ func (o *OAuth2) checkOAuth(w http.ResponseWriter, r *http.Request, allowRedirec
return nil, false return nil, false
} }
delete(o._nonces, state) delete(o._nonces, state)
oauth2Token, err := o._oauth2Config.Exchange(o._ctx, code) oauth2Token, err := o._oauth2Config.Exchange(o._ctx, code)
if err != nil { if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError) http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
@@ -72,7 +76,7 @@ func (o *OAuth2) checkOAuth(w http.ResponseWriter, r *http.Request, allowRedirec
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError) http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
return nil, false return nil, false
} }
log.Info(rawIDToken)
token, err2 := o._oidcVerifier.Verify(o._ctx, rawIDToken) token, err2 := o._oidcVerifier.Verify(o._ctx, rawIDToken)
if err2 != nil { if err2 != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError) http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
@@ -99,9 +103,10 @@ func (o *OAuth2) checkOAuth(w http.ResponseWriter, r *http.Request, allowRedirec
} else { } else {
//no auth code and no bearer -> redirect //no auth code and no bearer -> redirect
if allowRedirect { if allowRedirect {
nonce := generateToken() pw, _ , _ := generateToken();
o._nonces[nonce] = nonce o._nonces[pw] = pw
http.Redirect(w, r, o._oauth2Config.AuthCodeURL(nonce), http.StatusFound)
http.Redirect(w, r, o._oauth2Config.AuthCodeURL(pw), http.StatusFound)
} }
return nil, false return nil, false
@@ -123,7 +128,6 @@ func CreateOAuth2(config *Config) (*OAuth2, error) {
// Configure an OpenID Connect aware OAuth2 client. // Configure an OpenID Connect aware OAuth2 client.
verifier := provider.Verifier(oidcConfig) verifier := provider.Verifier(oidcConfig)
oauthConfig := oauth2.Config{ oauthConfig := oauth2.Config{
ClientID: config.OAuth2ClientID, ClientID: config.OAuth2ClientID,
ClientSecret: config.OAuth2ClientSecret, ClientSecret: config.OAuth2ClientSecret,
@@ -132,11 +136,15 @@ func CreateOAuth2(config *Config) (*OAuth2, error) {
Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
} }
return &OAuth2{ ret := OAuth2{
_ctx: ctx, _ctx: ctx,
_oauth2Config: &oauthConfig, _oauth2Config: &oauthConfig,
_oidcVerifier: verifier, _oidcVerifier: verifier,
_oidcProvider: provider, _oidcProvider: provider,
_nonces: make(map[string]string), _nonces: make(map[string]string),
}, nil }
err = provider.Claims(&ret)
return &ret, nil
} }

View File

@@ -7,10 +7,10 @@ import (
type AdminPageParams struct { type AdminPageParams struct {
Hosts map[string]string Hosts map[string]string
Email string Claims OidcClams
Profile string
Alerts []Alert Alerts []Alert
IpAddresses map[string][]string IpAddresses map[string][]string
LogoutUrl string
} }

View File

@@ -13,12 +13,11 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-8"> <div class="col-8">
<h5 class="card-title">Logged in as {{.Email}}</h5> <h5 class="card-title">Logged in as {{.Claims.Username}} ({{.Claims.Email}})</h5>
</div> </div>
<div class="col-4"> <div class="col-4">
<a class="btn btn-outline-primary" href="#">Logout</a> <a class="btn btn-outline-primary" href="{{.LogoutUrl}}">Logout</a>
</div> </div>
<p class="card-text">{{.Profile}}</p>
</div> </div>
</div> </div>