mirror of
https://github.com/boringproxy/boringproxy.git
synced 2024-07-07 04:42:58 -05:00
Limit token permissions
Added the ability to scope tokens to a specific client. If enabled, this has the affect of limiting the token to being used to list tunnels for that specific client. It can't be used for the web UI or for any state-changing actions such as creating new tunnels.
This commit is contained in:
parent
392a1ec8d7
commit
0a23c2fc0e
63
api.go
63
api.go
|
@ -59,8 +59,24 @@ func (a *Api) handleTunnels(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
tunnels := a.GetTunnels(tokenData)
|
||||
|
||||
if len(query["client-name"]) == 1 {
|
||||
clientName := query["client-name"][0]
|
||||
// If the token is limited to a specific client, filter out
|
||||
// tunnels for any other clients.
|
||||
if tokenData.Client != "" {
|
||||
for k, tun := range tunnels {
|
||||
if tokenData.Client != tun.ClientName {
|
||||
delete(tunnels, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clientName := query.Get("client-name")
|
||||
if clientName != "" && tokenData.Client != "" && clientName != tokenData.Client {
|
||||
w.WriteHeader(403)
|
||||
w.Write([]byte("Token is not valid for this client"))
|
||||
return
|
||||
}
|
||||
|
||||
if clientName != "" {
|
||||
for k, tun := range tunnels {
|
||||
if tun.ClientName != clientName {
|
||||
delete(tunnels, k)
|
||||
|
@ -85,6 +101,13 @@ func (a *Api) handleTunnels(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
w.Write([]byte(body))
|
||||
case "POST":
|
||||
|
||||
if tokenData.Client != "" {
|
||||
w.WriteHeader(403)
|
||||
io.WriteString(w, fmt.Sprintf("Token can only be used to list tunnels for client %s", tokenData.Client))
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
_, err := a.CreateTunnel(tokenData, r.Form)
|
||||
if err != nil {
|
||||
|
@ -92,6 +115,12 @@ func (a *Api) handleTunnels(w http.ResponseWriter, r *http.Request) {
|
|||
w.Write([]byte(err.Error()))
|
||||
}
|
||||
case "DELETE":
|
||||
if tokenData.Client != "" {
|
||||
w.WriteHeader(403)
|
||||
io.WriteString(w, fmt.Sprintf("Token can only be used to list tunnels for client %s", tokenData.Client))
|
||||
return
|
||||
}
|
||||
|
||||
r.ParseForm()
|
||||
err := a.DeleteTunnel(tokenData, r.Form)
|
||||
if err != nil {
|
||||
|
@ -119,6 +148,12 @@ func (a *Api) handleUsers(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if tokenData.Client != "" {
|
||||
w.WriteHeader(403)
|
||||
io.WriteString(w, fmt.Sprintf("Token can only be used to list tunnels for client %s", tokenData.Client))
|
||||
return
|
||||
}
|
||||
|
||||
path := r.URL.Path
|
||||
parts := strings.Split(path[1:], "/")
|
||||
|
||||
|
@ -178,6 +213,12 @@ func (a *Api) handleTokens(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if tokenData.Client != "" {
|
||||
w.WriteHeader(403)
|
||||
io.WriteString(w, fmt.Sprintf("Token can only be used to list tunnels for client %s", tokenData.Client))
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
r.ParseForm()
|
||||
|
@ -326,6 +367,7 @@ func (a *Api) CreateTunnel(tokenData TokenData, params url.Values) (*Tunnel, err
|
|||
}
|
||||
|
||||
func (a *Api) DeleteTunnel(tokenData TokenData, params url.Values) error {
|
||||
|
||||
domain := params.Get("domain")
|
||||
if domain == "" {
|
||||
return errors.New("Invalid domain parameter")
|
||||
|
@ -355,14 +397,21 @@ func (a *Api) CreateToken(tokenData TokenData, params url.Values) (string, error
|
|||
return "", errors.New("Invalid owner paramater")
|
||||
}
|
||||
|
||||
if tokenData.Owner != owner {
|
||||
user, _ := a.db.GetUser(tokenData.Owner)
|
||||
if !user.IsAdmin {
|
||||
return "", errors.New("Unauthorized")
|
||||
user, _ := a.db.GetUser(tokenData.Owner)
|
||||
|
||||
if tokenData.Owner != owner && !user.IsAdmin {
|
||||
return "", errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
client := params.Get("client")
|
||||
|
||||
if client != "any" {
|
||||
if _, exists := user.Clients[client]; !exists {
|
||||
return "", errors.New(fmt.Sprintf("Client %s does not exist for user %s", client, owner))
|
||||
}
|
||||
}
|
||||
|
||||
token, err := a.db.AddToken(owner)
|
||||
token, err := a.db.AddToken(owner, client)
|
||||
if err != nil {
|
||||
return "", errors.New("Failed to create token")
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ func Listen() {
|
|||
users := db.GetUsers()
|
||||
if len(users) == 0 {
|
||||
db.AddUser("admin", true)
|
||||
_, err := db.AddToken("admin")
|
||||
_, err := db.AddToken("admin", "any")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to initialize admin user")
|
||||
}
|
||||
|
|
10
database.go
10
database.go
|
@ -20,7 +20,8 @@ type Database struct {
|
|||
}
|
||||
|
||||
type TokenData struct {
|
||||
Owner string `json:"owner"`
|
||||
Owner string `json:"owner"`
|
||||
Client string `json:"client,omitempty"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
|
@ -142,7 +143,7 @@ func (d *Database) DeleteDNSRequest(requestId string) {
|
|||
delete(d.dnsRequests, requestId)
|
||||
}
|
||||
|
||||
func (d *Database) AddToken(owner string) (string, error) {
|
||||
func (d *Database) AddToken(owner, client string) (string, error) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
|
@ -156,7 +157,10 @@ func (d *Database) AddToken(owner string) (string, error) {
|
|||
return "", errors.New("Could not generat token")
|
||||
}
|
||||
|
||||
d.Tokens[token] = TokenData{owner}
|
||||
d.Tokens[token] = TokenData{
|
||||
Owner: owner,
|
||||
Client: client,
|
||||
}
|
||||
|
||||
d.persist()
|
||||
|
||||
|
|
14
templates/add_token_client.tmpl
Normal file
14
templates/add_token_client.tmpl
Normal file
|
@ -0,0 +1,14 @@
|
|||
{{ template "header.tmpl" . }}
|
||||
<h1>Add Token</h1>
|
||||
<form action="/tokens" method="POST">
|
||||
<input type="hidden" name="owner" value="{{$.Owner}}">
|
||||
<label for="token-client">Limit to client:</label>
|
||||
<select id="token-client" name="client">
|
||||
<option value="any">No</option>
|
||||
{{range $clientName, $c := $.User.Clients}}
|
||||
<option value="{{$clientName}}">{{$clientName}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
<button class='button' type="submit">Submit</button>
|
||||
</form>
|
||||
{{ template "footer.tmpl" . }}
|
|
@ -14,7 +14,7 @@
|
|||
</div>
|
||||
|
||||
<div class='token-adder'>
|
||||
<form action="/tokens" method="POST">
|
||||
<form action="/add-token-client" method="POST">
|
||||
<label for="token-owner">Owner:</label>
|
||||
<select id="token-owner" name="owner">
|
||||
{{range $username, $user := .Users}}
|
||||
|
|
|
@ -89,6 +89,12 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
|||
return
|
||||
}
|
||||
|
||||
if tokenData.Client != "" {
|
||||
w.WriteHeader(403)
|
||||
h.alertDialog(w, r, "This token is limited to a specific client and cannot be used for the web UI", "/")
|
||||
return
|
||||
}
|
||||
|
||||
user, _ := h.db.GetUser(tokenData.Owner)
|
||||
|
||||
tunnels := h.api.GetTunnels(tokenData)
|
||||
|
@ -206,6 +212,27 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
|||
w.Header().Set("Content-Disposition", "attachment; filename=id_rsa")
|
||||
io.WriteString(w, tun.TunnelPrivateKey)
|
||||
|
||||
case "/add-token-client":
|
||||
r.ParseForm()
|
||||
|
||||
owner := r.Form.Get("owner")
|
||||
|
||||
addTokenUser, _ := h.db.GetUser(owner)
|
||||
|
||||
templateData := struct {
|
||||
Owner string
|
||||
User User
|
||||
}{
|
||||
Owner: owner,
|
||||
User: addTokenUser,
|
||||
}
|
||||
|
||||
err := h.tmpl.ExecuteTemplate(w, "add_token_client.tmpl", templateData)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, err.Error())
|
||||
return
|
||||
}
|
||||
case "/tokens":
|
||||
h.handleTokens(w, r, user, tokenData)
|
||||
case "/confirm-delete-token":
|
||||
|
|
Loading…
Reference in New Issue
Block a user