Implement admin domain with bootstrap domain

TakingNames.io now provides bootstrap domains, which simply
creates an A record based off the ip address of the requesting
server, without requiring any authorization. This allows
boringproxy to always use HTTPS, even when getting the admin
domain.
This commit is contained in:
Anders Pitman
2021-12-19 21:24:52 -07:00
parent 8653583cec
commit 7c6077a0fe
4 changed files with 74 additions and 63 deletions

View File

@@ -2,7 +2,6 @@ package boringproxy
import (
"bufio"
"context"
"crypto/tls"
"encoding/json"
"errors"
@@ -21,7 +20,6 @@ import (
)
type Config struct {
WebUiDomain string `json:"webui_domain"`
SshServerPort int `json:"ssh_server_port"`
PublicIp string `json:"public_ip"`
}
@@ -153,9 +151,10 @@ func Listen() {
if adminDomain == "" {
adminDomain = getAdminDomain(ip, certConfig)
db.SetAdminDomain(adminDomain)
err = setAdminDomain(ip, certConfig, db)
if err != nil {
log.Fatal(err)
}
} else {
err = certConfig.ManageSync([]string{adminDomain})
if err != nil {
@@ -167,17 +166,17 @@ func Listen() {
users := db.GetUsers()
if len(users) == 0 {
db.AddUser("admin", true)
token, err := db.AddToken("admin")
//token, err := db.AddToken("admin")
_, err := db.AddToken("admin")
if err != nil {
log.Fatal("Failed to initialize admin user")
}
log.Println("Admin token: " + token)
log.Println(fmt.Sprintf("Admin login link: https://%s/login?access_token=%s", adminDomain, token))
//log.Println("Admin token: " + token)
//log.Println(fmt.Sprintf("Admin login link: https://%s/login?access_token=%s", adminDomain, token))
}
config := &Config{
WebUiDomain: adminDomain,
SshServerPort: *sshServerPort,
PublicIp: ip,
}
@@ -238,7 +237,7 @@ func Listen() {
requestId := r.Form.Get("request-id")
// Ensure the request exists
_, err := db.GetDNSRequest(requestId)
dnsRequest, err := db.GetDNSRequest(requestId)
if err != nil {
w.WriteHeader(500)
io.WriteString(w, err.Error())
@@ -248,8 +247,23 @@ func Listen() {
db.DeleteDNSRequest(requestId)
domain := r.Form.Get("domain")
http.Redirect(w, r, fmt.Sprintf("https://%s/edit-tunnel?domain=%s", config.WebUiDomain, domain), 303)
} else if r.Host == config.WebUiDomain {
if dnsRequest.IsAdminDomain {
db.SetAdminDomain(domain)
// TODO: Might want to get all certs here, not just the admin domain
err := certConfig.ManageSync([]string{domain})
if err != nil {
log.Fatal(err)
}
http.Redirect(w, r, fmt.Sprintf("https://%s", domain), 303)
} else {
adminDomain := db.GetAdminDomain()
http.Redirect(w, r, fmt.Sprintf("https://%s/edit-tunnel?domain=%s", adminDomain, domain), 303)
}
} else if r.Host == db.GetAdminDomain() {
if strings.HasPrefix(r.URL.Path, "/api/") {
http.StripPrefix("/api", api).ServeHTTP(w, r)
} else {
@@ -342,72 +356,66 @@ func (p *Server) passthroughRequest(conn net.Conn, tunnel Tunnel) {
wg.Wait()
}
func getAdminDomain(ip string, certConfig *certmagic.Config) string {
func setAdminDomain(ip string, certConfig *certmagic.Config, db *Database) error {
action := prompt("\nNo admin domain set. Enter '1' to input manually, or '2' to configure through TakingNames.io\n")
var adminDomain string
switch action {
case "1":
adminDomain = prompt("\nEnter admin domain:\n")
adminDomain := prompt("\nEnter admin domain:\n")
err := certConfig.ManageSync([]string{adminDomain})
if err != nil {
log.Fatal(err)
}
db.SetAdminDomain(adminDomain)
case "2":
log.Println("Get bootstrap domain")
resp, err := http.Get("https://takingnames.io/dnsapi/bootstrap-domain")
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
bootstrapDomain := string(body)
if resp.StatusCode != 200 {
fmt.Println(bootstrapDomain)
return errors.New("bootstrap domain request failed")
}
log.Println("Get cert")
err = certConfig.ManageSync([]string{bootstrapDomain})
if err != nil {
log.Fatal(err)
}
requestId, _ := genRandomCode(32)
tnLink := fmt.Sprintf("https://takingnames.io/dnsapi?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
mux := http.NewServeMux()
server := http.Server{
Addr: ":80",
Handler: mux,
req := DNSRequest{
IsAdminDomain: true,
Records: []*DNSRecord{
&DNSRecord{
Type: "A",
Value: ip,
TTL: 300,
},
},
}
mux.HandleFunc("/dnsapi/callback", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
db.SetDNSRequest(requestId, req)
domain := r.Form.Get("domain")
returnedReqId := r.Form.Get("request-id")
if domain == "" {
log.Fatal("Blank domain from TakingNames.io")
}
if returnedReqId != requestId {
log.Fatal("request-id doesn't match")
}
adminDomain = domain
err := certConfig.ManageSync([]string{adminDomain})
if err != nil {
log.Fatal(err)
}
go func() {
ctx := context.Background()
server.Shutdown(ctx)
}()
http.Redirect(w, r, fmt.Sprintf("https://%s", adminDomain), 303)
})
fmt.Println("Use the link below to select a domain:\n\n" + tnLink)
server.ListenAndServe()
tnLink := fmt.Sprintf("https://takingnames.io/dnsapi?requester=%s&request-id=%s", bootstrapDomain, requestId)
fmt.Println("Use the link below to select an admin domain:\n\n" + tnLink + "\n")
default:
log.Fatal("Invalid option")
}
return adminDomain
return nil
}
func prompt(promptText string) string {

View File

@@ -36,7 +36,8 @@ type DbClient struct {
}
type DNSRequest struct {
Records []*DNSRecord `json:"records"`
IsAdminDomain bool `json:"is_admin_domain"`
Records []*DNSRecord `json:"records"`
}
type DNSRecord struct {

View File

@@ -79,7 +79,6 @@ func (m *TunnelManager) RequestCreateTunnel(tunReq Tunnel) (Tunnel, error) {
}
for _, tun := range m.db.GetTunnels() {
fmt.Println(tunReq.Domain, tun.Domain)
if tunReq.Domain == tun.Domain {
return Tunnel{}, errors.New("Tunnel domain already in use")
}
@@ -94,7 +93,7 @@ func (m *TunnelManager) RequestCreateTunnel(tunReq Tunnel) (Tunnel, error) {
return Tunnel{}, err
}
tunReq.ServerAddress = m.config.WebUiDomain
tunReq.ServerAddress = m.db.GetAdminDomain()
tunReq.ServerPort = m.config.SshServerPort
tunReq.ServerPublicKey = ""
tunReq.Username = m.user.Username

View File

@@ -178,7 +178,9 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
h.db.SetDNSRequest(requestId, req)
tnLink := fmt.Sprintf("https://takingnames.io/dnsapi?requester=%s&request-id=%s", h.config.WebUiDomain, requestId)
adminDomain := h.db.GetAdminDomain()
tnLink := fmt.Sprintf("https://takingnames.io/dnsapi?requester=%s&request-id=%s", adminDomain, requestId)
templateData := struct {
Domain string
@@ -351,7 +353,8 @@ func (h *WebUiHandler) handleTokens(w http.ResponseWriter, r *http.Request, user
qrCodes := make(map[string]template.URL)
for token := range tokens {
loginUrl := fmt.Sprintf("https://%s/login?access_token=%s", h.config.WebUiDomain, token)
adminDomain := h.db.GetAdminDomain()
loginUrl := fmt.Sprintf("https://%s/login?access_token=%s", adminDomain, token)
var png []byte
png, err := qrcode.Encode(loginUrl, qrcode.Medium, 256)