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
import (
"crypto/subtle"
"encoding/json"
"fmt"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"net"
"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":
host := r.URL.Query().Get("host")
_, err := h._database.CreateHost(host)
pw, _, err := h._database.CreateHost(host)
if err == nil {
params.Alerts = append(params.Alerts, Alert{
Type: "success",
Message: fmt.Sprintf("created host %s", host),
Message: fmt.Sprintf("created host %s\npassword: %s", host, pw),
})
} else {
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.Hosts = make(map[string]string)
params.IpAddresses = h._ipAddresses
params.LogoutUrl = h._oauth.LogoutUrl
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)
params.Email = claims.Email
params.Profile = claims.Profile
params.Claims = *claims
if settings.FilterString != nil {
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("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)
}
}
}
func (h *HttpServer) Listen() {
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 {
var httpserver *HttpServer
@@ -315,6 +339,10 @@ func CreateHttpServer(config *Config) *HttpServer {
Handler: r,
}
auth := auth.NewBasicAuthenticator(config.AuthRealm, func(user string) {
return httpserver._database._db.Get("hosts/"+ user)
})
r.HandleFunc("/register", httpserver.registerHandler)
r.HandleFunc("/update", httpserver.updateHandler)
r.HandleFunc("/admin", httpserver.adminPage)

View File

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

View File

@@ -1,23 +1,19 @@
package main
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"errors"
"github.com/miekg/dns"
"github.com/patrickmn/go-cache"
password "github.com/sethvargo/go-password/password"
log "github.com/sirupsen/logrus"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
"math"
"math/big"
bcrypt "golang.org/x/crypto/bcrypt"
"os"
"path"
"strings"
"time"
"github.com/patrickmn/go-cache"
)
type Database struct {
@@ -57,11 +53,11 @@ func (d *Database) Authorize(host string, token_user string) bool {
return false
}
if len(token) < sha512.Size {
if len(token) < sha256.Size {
return false
}
if len(token_user) < sha512.Size {
if len(token_user) < sha256.Size {
return false
}
@@ -74,24 +70,21 @@ func (d *Database) Authorize(host string, token_user string) bool {
return false
}
func generateToken() string {
maxInt := big.NewInt(math.MaxInt64)
randInt, err := rand.Int(rand.Reader, maxInt)
func generateToken() (string, []byte, error) {
pw, err := password.Generate(64, 10, 10, false, false)
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++ {
h = sha256.Sum256(h[:])
if errHash != nil {
return "", nil , errHash
}
var buf bytes.Buffer
base64.NewEncoder(base64.URLEncoding, &buf).Write(h[:])
return buf.String()
return pw, hash, nil
}
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) {
return "", errors.New("host already existent")
return "", nil, errors.New("host already existent")
}
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 {
return "", errors.New("given hostname is invalid")
return "", nil, errors.New("given hostname is invalid")
}
token := generateToken()
d._db.Put([]byte("hosts/" + host), []byte(token), nil)
pw, hash, errToken := generateToken()
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
require (
github.com/abbot/go-http-auth v0.4.0
github.com/coreos/go-oidc/v3 v3.0.0
github.com/gorilla/mux v1.8.0
github.com/miekg/dns v1.1.35
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/syndtr/goleveldb v1.0.0
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
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=
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/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

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

View File

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

View File

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

View File

@@ -13,12 +13,11 @@
<div class="container">
<div class="row">
<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 class="col-4">
<a class="btn btn-outline-primary" href="#">Logout</a>
<a class="btn btn-outline-primary" href="{{.LogoutUrl}}">Logout</a>
</div>
<p class="card-text">{{.Profile}}</p>
</div>
</div>