proxy: Prepare for different token storages.

This commit is contained in:
Joachim Bauch 2020-08-28 10:31:11 +02:00
parent 2d73b97882
commit 85d6726d59
Failed to extract signature
4 changed files with 196 additions and 75 deletions

View file

@ -12,6 +12,13 @@
# servers to determine the closest proxy for publishers.
#country = DE
# Type of token configuration for signaling servers allowed to connect, see
# below for details. Defaults to "static".
#
# Possible values:
# - static: A mapping of token id -> public key is configured below.
token_type = static
[sessions]
# Secret value used to generate checksums of sessions. This should be a random
# string of 32 or 64 bytes.
@ -30,7 +37,8 @@ blockkey = -encryption-key-
#url = nats://localhost:4222
[tokens]
# Mapping of <tokenid> = <publickey> of signaling servers allowed to connect.
# For token_type "static": Mapping of <tokenid> = <publickey> of signaling
# servers allowed to connect.
#server1 = pubkey1.pem
#server2 = pubkey2.pem

View file

@ -22,10 +22,8 @@
package main
import (
"crypto/rsa"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
@ -33,7 +31,6 @@ import (
"os"
"os/signal"
runtimepprof "runtime/pprof"
"sort"
"strings"
"sync"
"sync/atomic"
@ -98,7 +95,7 @@ type ProxyServer struct {
upgrader websocket.Upgrader
tokenKeys atomic.Value
tokens ProxyTokens
statsAllowedIps map[string]bool
sid uint64
@ -132,32 +129,22 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile, na
return nil, fmt.Errorf("The sessions block key must be 16, 24 or 32 bytes but is %d bytes", len(blockKey))
}
tokenKeys := make(map[string]*rsa.PublicKey)
options, _ := config.GetOptions("tokens")
for _, id := range options {
filename, _ := config.GetString("tokens", id)
if filename == "" {
return nil, fmt.Errorf("No filename given for token %s", id)
}
keyData, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("Could not read public key from %s: %s", filename, err)
}
key, err := jwt.ParseRSAPublicKeyFromPEM(keyData)
if err != nil {
return nil, fmt.Errorf("Could not parse public key from %s: %s", filename, err)
}
tokenKeys[id] = key
var tokens ProxyTokens
var err error
tokenType, _ := config.GetString("app", "token_type")
if tokenType == "" {
tokenType = TokenTypeDefault
}
var keyIds []string
for k, _ := range tokenKeys {
keyIds = append(keyIds, k)
switch tokenType {
case TokenTypeStatic:
tokens, err = NewProxyTokensStatic(config)
default:
return nil, fmt.Errorf("Unsupported token type configured: %s", tokenType)
}
if err != nil {
return nil, err
}
sort.Strings(keyIds)
log.Printf("Enabled token keys: %v", keyIds)
statsAllowed, _ := config.GetString("stats", "allowed_ips")
var statsAllowedIps map[string]bool
@ -200,6 +187,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile, na
WriteBufferSize: websocketWriteBufferSize,
},
tokens: tokens,
statsAllowedIps: statsAllowedIps,
cookie: securecookie.New([]byte(hashKey), blockBytes).MaxAge(0),
@ -209,7 +197,6 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile, na
clientIds: make(map[string]string),
}
result.setTokenKeys(tokenKeys)
result.upgrader.CheckOrigin = result.checkOrigin
if debug, _ := config.GetBool("app", "debug"); debug {
@ -235,14 +222,6 @@ func (s *ProxyServer) checkOrigin(r *http.Request) bool {
return true
}
func (s *ProxyServer) setTokenKeys(keys map[string]*rsa.PublicKey) {
s.tokenKeys.Store(keys)
}
func (s *ProxyServer) getTokenKeys() map[string]*rsa.PublicKey {
return s.tokenKeys.Load().(map[string]*rsa.PublicKey)
}
func (s *ProxyServer) Start(config *goconf.ConfigFile) error {
s.url, _ = config.GetString("mcu", "url")
if s.url == "" {
@ -413,40 +392,7 @@ func (s *ProxyServer) ScheduleShutdown() {
}
func (s *ProxyServer) Reload(config *goconf.ConfigFile) {
tokenKeys := make(map[string]*rsa.PublicKey)
options, _ := config.GetOptions("tokens")
for _, id := range options {
filename, _ := config.GetString("tokens", id)
if filename == "" {
log.Printf("No filename given for token %s, ignoring", id)
continue
}
keyData, err := ioutil.ReadFile(filename)
if err != nil {
log.Printf("Could not read public key from %s, ignoring: %s", filename, err)
continue
}
key, err := jwt.ParseRSAPublicKeyFromPEM(keyData)
if err != nil {
log.Printf("Could not parse public key from %s, ignoring: %s", filename, err)
continue
}
tokenKeys[id] = key
}
if len(tokenKeys) == 0 {
log.Printf("No token keys loaded")
} else {
var keyIds []string
for k, _ := range tokenKeys {
keyIds = append(keyIds, k)
}
sort.Strings(keyIds)
log.Printf("Enabled token keys: %v", keyIds)
}
s.setTokenKeys(tokenKeys)
s.tokens.Reload(config)
}
func (s *ProxyServer) setCommonHeaders(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
@ -878,13 +824,17 @@ func (s *ProxyServer) NewSession(hello *signaling.HelloProxyClientMessage) (*Pro
return nil, fmt.Errorf("Unsupported claims type")
}
tokenKeys := s.getTokenKeys()
publicKey := tokenKeys[claims.Issuer]
if publicKey == nil {
tokenKey, err := s.tokens.Get(claims.Issuer)
if err != nil {
log.Printf("Could not get token for %s: %s", claims.Issuer, err)
return nil, err
}
if tokenKey == nil || tokenKey.key == nil {
log.Printf("Issuer %s is not supported", claims.Issuer)
return nil, fmt.Errorf("No key found for issuer")
}
return publicKey, nil
return tokenKey.key, nil
})
if err != nil {
return nil, TokenAuthFailed

45
src/proxy/proxy_tokens.go Normal file
View file

@ -0,0 +1,45 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2020 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"crypto/rsa"
"github.com/dlintw/goconf"
)
const (
TokenTypeStatic = "static"
TokenTypeDefault = TokenTypeStatic
)
type ProxyToken struct {
id string
key *rsa.PublicKey
}
type ProxyTokens interface {
Get(id string) (*ProxyToken, error)
Reload(config *goconf.ConfigFile)
}

View file

@ -0,0 +1,118 @@
/**
* Standalone signaling server for the Nextcloud Spreed app.
* Copyright (C) 2020 struktur AG
*
* @author Joachim Bauch <bauch@struktur.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"io/ioutil"
"log"
"sort"
"sync/atomic"
"github.com/dlintw/goconf"
"gopkg.in/dgrijalva/jwt-go.v3"
)
type tokensStatic struct {
tokenKeys atomic.Value
}
func NewProxyTokensStatic(config *goconf.ConfigFile) (ProxyTokens, error) {
result := &tokensStatic{}
if err := result.load(config, false); err != nil {
return nil, err
}
return result, nil
}
func (t *tokensStatic) setTokenKeys(keys map[string]*ProxyToken) {
t.tokenKeys.Store(keys)
}
func (t *tokensStatic) getTokenKeys() map[string]*ProxyToken {
return t.tokenKeys.Load().(map[string]*ProxyToken)
}
func (t *tokensStatic) Get(id string) (*ProxyToken, error) {
tokenKeys := t.getTokenKeys()
token := tokenKeys[id]
return token, nil
}
func (t *tokensStatic) load(config *goconf.ConfigFile, ignoreErrors bool) error {
tokenKeys := make(map[string]*ProxyToken)
options, _ := config.GetOptions("tokens")
for _, id := range options {
filename, _ := config.GetString("tokens", id)
if filename == "" {
if !ignoreErrors {
return fmt.Errorf("No filename given for token %s", id)
}
log.Printf("No filename given for token %s, ignoring", id)
continue
}
keyData, err := ioutil.ReadFile(filename)
if err != nil {
if !ignoreErrors {
return fmt.Errorf("Could not read public key from %s: %s", filename, err)
}
log.Printf("Could not read public key from %s, ignoring: %s", filename, err)
continue
}
key, err := jwt.ParseRSAPublicKeyFromPEM(keyData)
if err != nil {
if !ignoreErrors {
return fmt.Errorf("Could not parse public key from %s: %s", filename, err)
}
log.Printf("Could not parse public key from %s, ignoring: %s", filename, err)
continue
}
tokenKeys[id] = &ProxyToken{
id: id,
key: key,
}
}
if len(tokenKeys) == 0 {
log.Printf("No token keys loaded")
} else {
var keyIds []string
for k, _ := range tokenKeys {
keyIds = append(keyIds, k)
}
sort.Strings(keyIds)
log.Printf("Enabled token keys: %v", keyIds)
}
t.setTokenKeys(tokenKeys)
return nil
}
func (t *tokensStatic) Reload(config *goconf.ConfigFile) {
t.load(config, true)
}