mirror of
https://github.com/boringproxy/boringproxy.git
synced 2025-02-25 18:55:29 -06:00
Improve security of TakingNames.io integration
The requests themselves now must be retrieve from the boringproxy server by TakingNames.io, over HTTPS. This provides several security benefits: * You can tell the user the request is coming from a specific domain. * Requests are tied to an ephemeral request-id, to prevent prebuilt phishing links. There is currently a single hard-coded exception for setting a single A record for an IP address. This is needed for bootstrapping a service that doesn't have any certs yet (ie the boringproxy admin domain), and will need to display a big scary message to users.
This commit is contained in:
@@ -12,7 +12,6 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -212,11 +211,43 @@ func Listen() {
|
||||
timestamp := time.Now().Format(time.RFC3339)
|
||||
srcIp := strings.Split(r.RemoteAddr, ":")[0]
|
||||
fmt.Println(fmt.Sprintf("%s %s %s %s %s", timestamp, srcIp, r.Method, r.Host, r.URL.Path))
|
||||
if r.URL.Path == "/domain-callback" {
|
||||
if r.URL.Path == "/webdo/requests" {
|
||||
r.ParseForm()
|
||||
|
||||
requestId := r.Form.Get("request-id")
|
||||
|
||||
dnsRequest, err := db.GetDNSRequest(requestId)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(dnsRequest)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(jsonBytes)
|
||||
|
||||
} else if r.URL.Path == "/webdo/callback" {
|
||||
r.ParseForm()
|
||||
|
||||
requestId := r.Form.Get("request-id")
|
||||
|
||||
// Ensure the request exists
|
||||
_, err := db.GetDNSRequest(requestId)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
db.DeleteDNSRequest(requestId)
|
||||
|
||||
domain := r.Form.Get("domain")
|
||||
// TODO: Check request ID
|
||||
http.Redirect(w, r, fmt.Sprintf("https://%s/edit-tunnel?domain=%s", config.WebUiDomain, domain), 303)
|
||||
} else if r.Host == config.WebUiDomain {
|
||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||
@@ -328,24 +359,7 @@ func getAdminDomain(ip string, certConfig *certmagic.Config) string {
|
||||
|
||||
requestId, _ := genRandomCode(32)
|
||||
|
||||
req := &Request{
|
||||
RequestId: requestId,
|
||||
RedirectUri: fmt.Sprintf("http://%s/domain-callback", ip),
|
||||
Records: []*Record{
|
||||
&Record{
|
||||
Type: "A",
|
||||
Value: ip,
|
||||
TTL: 300,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
tnLink := "https://takingnames.io/approve?r=" + url.QueryEscape(string(jsonBytes))
|
||||
tnLink := fmt.Sprintf("https://takingnames.io/webdo?requester=%s&request-id=%s&request-type=%s", ip, requestId, "set-ip")
|
||||
|
||||
// Create a temporary web server to handle the callback which contains the domain
|
||||
|
||||
@@ -356,7 +370,7 @@ func getAdminDomain(ip string, certConfig *certmagic.Config) string {
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/domain-callback", func(w http.ResponseWriter, r *http.Request) {
|
||||
mux.HandleFunc("/webdo/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
|
||||
domain := r.Form.Get("domain")
|
||||
@@ -372,7 +386,7 @@ func getAdminDomain(ip string, certConfig *certmagic.Config) string {
|
||||
|
||||
adminDomain = domain
|
||||
|
||||
err = certConfig.ManageSync([]string{adminDomain})
|
||||
err := certConfig.ManageSync([]string{adminDomain})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
53
database.go
53
database.go
@@ -9,11 +9,12 @@ import (
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
AdminDomain string `json:"admin_domain"`
|
||||
Tokens map[string]TokenData `json:"tokens"`
|
||||
Tunnels map[string]Tunnel `json:"tunnels"`
|
||||
Users map[string]User `json:"users"`
|
||||
SshKeys map[string]SshKey `json:"ssh_keys"`
|
||||
AdminDomain string `json:"admin_domain"`
|
||||
Tokens map[string]TokenData `json:"tokens"`
|
||||
Tunnels map[string]Tunnel `json:"tunnels"`
|
||||
Users map[string]User `json:"users"`
|
||||
SshKeys map[string]SshKey `json:"ssh_keys"`
|
||||
dnsRequests map[string]DNSRequest `json:"dns_requests"`
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
@@ -34,6 +35,16 @@ type SshKey struct {
|
||||
type DbClient struct {
|
||||
}
|
||||
|
||||
type DNSRequest struct {
|
||||
Records []*DNSRecord `json:"records"`
|
||||
}
|
||||
|
||||
type DNSRecord struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
type Tunnel struct {
|
||||
Owner string `json:"owner"`
|
||||
Domain string `json:"domain"`
|
||||
@@ -86,6 +97,10 @@ func NewDatabase() (*Database, error) {
|
||||
db.SshKeys = make(map[string]SshKey)
|
||||
}
|
||||
|
||||
if db.dnsRequests == nil {
|
||||
db.dnsRequests = make(map[string]DNSRequest)
|
||||
}
|
||||
|
||||
db.mutex = &sync.Mutex{}
|
||||
|
||||
db.mutex.Lock()
|
||||
@@ -103,7 +118,6 @@ func (d *Database) SetAdminDomain(adminDomain string) {
|
||||
|
||||
d.persist()
|
||||
}
|
||||
|
||||
func (d *Database) GetAdminDomain() string {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
@@ -111,6 +125,33 @@ func (d *Database) GetAdminDomain() string {
|
||||
return d.AdminDomain
|
||||
}
|
||||
|
||||
func (d *Database) SetDNSRequest(requestId string, request DNSRequest) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
d.dnsRequests[requestId] = request
|
||||
|
||||
// Not currently persisting because dnsRequests is only stored in
|
||||
// memory. May change in the future.
|
||||
//d.persist()
|
||||
}
|
||||
func (d *Database) GetDNSRequest(requestId string) (DNSRequest, error) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
if req, ok := d.dnsRequests[requestId]; ok {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
return DNSRequest{}, errors.New("No such DNS Request")
|
||||
}
|
||||
func (d *Database) DeleteDNSRequest(requestId string) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
delete(d.dnsRequests, requestId)
|
||||
}
|
||||
|
||||
func (d *Database) AddToken(owner string) (string, error) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
@@ -3,14 +3,14 @@ package boringproxy
|
||||
import (
|
||||
"embed"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
//"encoding/json"
|
||||
"fmt"
|
||||
qrcode "github.com/skip2/go-qrcode"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
//"net/url"
|
||||
//"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -58,18 +58,6 @@ type LoginData struct {
|
||||
Head template.HTML
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
RequestId string `json:"request_id"`
|
||||
RedirectUri string `json:"redirect_uri"`
|
||||
Records []*Record `json:"records:`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
func NewWebUiHandler(config *Config, db *Database, api *Api, auth *Auth, tunMan *TunnelManager) *WebUiHandler {
|
||||
return &WebUiHandler{
|
||||
config: config,
|
||||
@@ -178,11 +166,9 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
||||
|
||||
requestId, _ := genRandomCode(32)
|
||||
|
||||
req := &Request{
|
||||
RequestId: requestId,
|
||||
RedirectUri: fmt.Sprintf("https://%s/domain-callback", h.config.WebUiDomain),
|
||||
Records: []*Record{
|
||||
&Record{
|
||||
req := DNSRequest{
|
||||
Records: []*DNSRecord{
|
||||
&DNSRecord{
|
||||
Type: "A",
|
||||
Value: h.config.PublicIp,
|
||||
TTL: 300,
|
||||
@@ -190,12 +176,9 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
||||
},
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
h.db.SetDNSRequest(requestId, req)
|
||||
|
||||
tnLink := "https://takingnames.io/approve?r=" + url.QueryEscape(string(jsonBytes))
|
||||
tnLink := fmt.Sprintf("https://takingnames.io/webdo?requester=%s&request-id=%s", h.config.WebUiDomain, requestId)
|
||||
|
||||
templateData := struct {
|
||||
Domain string
|
||||
|
||||
Reference in New Issue
Block a user