Skip to content
Snippets Groups Projects
Commit db67c6a8 authored by ale's avatar ale
Browse files

Support partitioned keystore service

Uses the clientutil.Backend abstraction for the keystore client API.
parent 4feb3be4
Branches
No related tags found
No related merge requests found
......@@ -19,7 +19,7 @@ import (
"github.com/crewjam/saml"
"github.com/crewjam/saml/logger"
"github.com/gorilla/mux"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"
"git.autistici.org/id/go-sso/httpsso"
)
......
......@@ -6,9 +6,9 @@ import (
"regexp"
"time"
ksclient "git.autistici.org/id/keystore/client"
"github.com/gorilla/securecookie"
"git.autistici.org/ai3/go-common/clientutil"
"git.autistici.org/id/go-sso/server/device"
)
......@@ -29,12 +29,12 @@ type Config struct {
TTLSeconds int `yaml:"ttl"`
rx *regexp.Regexp
} `yaml:"service_ttls"`
AuthSessionLifetimeSeconds int `yaml:"auth_session_lifetime"`
SessionSecrets []string `yaml:"session_secrets"`
CSRFSecret string `yaml:"csrf_secret"`
AuthService string `yaml:"auth_service"`
DeviceManager *device.Config `yaml:"device_manager"`
KeyStore *ksclient.Config `yaml:"keystore"`
AuthSessionLifetimeSeconds int `yaml:"auth_session_lifetime"`
SessionSecrets []string `yaml:"session_secrets"`
CSRFSecret string `yaml:"csrf_secret"`
AuthService string `yaml:"auth_service"`
DeviceManager *device.Config `yaml:"device_manager"`
KeyStore *clientutil.BackendConfig `yaml:"keystore"`
allowedServicesRx []*regexp.Regexp
}
......
......@@ -86,7 +86,7 @@ type Server struct {
authSessionLifetime time.Duration
loginHandler *loginHandler
loginService *LoginService
keystore *ksclient.Client
keystore ksclient.Client
csrfSecret []byte
tpl *template.Template
}
......@@ -147,7 +147,11 @@ func (h *Server) loginCallback(w http.ResponseWriter, req *http.Request, usernam
// authenticate. Set the TTL to the duration of the
// authenticated session.
if h.keystore != nil {
if err := h.keystore.Open(req.Context(), username, password, int(h.authSessionLifetime.Seconds())); err != nil {
var shard string
if userinfo != nil {
shard = userinfo.Shard
}
if err := h.keystore.Open(req.Context(), shard, username, password, int(h.authSessionLifetime.Seconds())); err != nil {
log.Printf("failed to unlock keystore for user %s: %v", username, err)
return err
}
......@@ -257,7 +261,11 @@ func (h *Server) handleLogout(w http.ResponseWriter, req *http.Request, session
// Close the keystore.
if h.keystore != nil {
if err := h.keystore.Close(req.Context(), session.Username); err != nil {
var shard string
if session.UserInfo != nil {
shard = session.UserInfo.Shard
}
if err := h.keystore.Close(req.Context(), shard, session.Username); err != nil {
log.Printf("failed to wipe keystore for user %s: %v", session.Username, err)
}
}
......
......@@ -46,7 +46,7 @@ auth_service: login
if keystoreURL != "" {
cfgstr += fmt.Sprintf(`
keystore:
backend_url: "%s"
url: "%s"
`, keystoreURL)
}
ioutil.WriteFile(filepath.Join(tmpdir, "config"), []byte(cfgstr), 0600)
......
package clientutil
import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"sync"
"time"
)
// BackendConfig specifies the configuration to access a service.
//
// Services with multiple backends can be replicated or partitioned,
// depending on a configuration switch, making it a deployment-time
// decision. Clients are expected to compute their own sharding
// function (either by database lookup or other methods), and expose a
// 'shard' parameter on their APIs.
type BackendConfig struct {
URL string `yaml:"url"`
Sharded bool `yaml:"sharded"`
TLSConfig *TLSClientConfig `yaml:"tls_config"`
}
// Backend is a runtime class that provides http Clients for use with
// a specific service backend. If the service can't be partitioned,
// pass an empty string to the Client method.
type Backend interface {
// URL for the service for a specific shard.
URL(string) string
// Client that can be used to make a request to the service.
Client(string) *http.Client
}
// NewBackend returns a new Backend with the given config.
func NewBackend(config *BackendConfig) (Backend, error) {
u, err := url.Parse(config.URL)
if err != nil {
return nil, err
}
var tlsConfig *tls.Config
if config.TLSConfig != nil {
tlsConfig, err = config.TLSConfig.TLSConfig()
if err != nil {
return nil, err
}
}
if config.Sharded {
return &replicatedClient{
u: u,
c: newHTTPClient(u, tlsConfig),
}, nil
}
return &shardedClient{
baseURL: u,
tlsConfig: tlsConfig,
urls: make(map[string]*url.URL),
shards: make(map[string]*http.Client),
}, nil
}
type replicatedClient struct {
c *http.Client
u *url.URL
}
func (r *replicatedClient) Client(_ string) *http.Client { return r.c }
func (r *replicatedClient) URL(_ string) string { return r.u.String() }
type shardedClient struct {
baseURL *url.URL
tlsConfig *tls.Config
mx sync.Mutex
urls map[string]*url.URL
shards map[string]*http.Client
}
func (s *shardedClient) getShardURL(shard string) *url.URL {
if shard == "" {
return s.baseURL
}
u, ok := s.urls[shard]
if !ok {
var tmp = *s.baseURL
tmp.Host = fmt.Sprintf("%s.%s", shard, tmp.Host)
u = &tmp
s.urls[shard] = u
}
return u
}
func (s *shardedClient) URL(shard string) string {
s.mx.Lock()
defer s.mx.Unlock()
return s.getShardURL(shard).String()
}
func (s *shardedClient) Client(shard string) *http.Client {
s.mx.Lock()
defer s.mx.Unlock()
client, ok := s.shards[shard]
if !ok {
u := s.getShardURL(shard)
client = newHTTPClient(u, s.tlsConfig)
s.shards[shard] = client
}
return client
}
func newHTTPClient(u *url.URL, tlsConfig *tls.Config) *http.Client {
return &http.Client{
Transport: NewTransport([]string{u.Host}, tlsConfig, nil),
Timeout: 30 * time.Second,
}
}
......@@ -2,10 +2,6 @@ package client
import (
"context"
"crypto/tls"
"net/http"
"net/url"
"time"
"git.autistici.org/ai3/go-common/clientutil"
......@@ -13,66 +9,50 @@ import (
)
// Client for the keystore API.
type Client struct {
*http.Client
backendURL string
type Client interface {
Open(context.Context, string, string, string, int) error
Get(context.Context, string, string, string) ([]byte, error)
Close(context.Context, string, string) error
}
// Config for a keystore client.
type Config struct {
BackendURL string `yaml:"backend_url"`
TLSConfig *clientutil.TLSClientConfig `yaml:"tls_config"`
type ksClient struct {
be clientutil.Backend
}
// New returns a new Client with the given Config.
func New(config *Config) (*Client, error) {
u, err := url.Parse(config.BackendURL)
// New returns a new Client with the given Config. Use this when the
// keystore service runs on a single global instance.
func New(config *clientutil.BackendConfig) (*ksClient, error) {
be, err := clientutil.NewBackend(config)
if err != nil {
return nil, err
}
var tlsConfig *tls.Config
if config.TLSConfig != nil {
tlsConfig, err = config.TLSConfig.TLSConfig()
if err != nil {
return nil, err
}
}
c := &http.Client{
Transport: clientutil.NewTransport([]string{u.Host}, tlsConfig, nil),
Timeout: 20 * time.Second,
}
return &Client{
Client: c,
backendURL: config.BackendURL,
}, nil
return &ksClient{be}, nil
}
func (c *Client) Open(ctx context.Context, username, password string, ttl int) error {
func (c *ksClient) Open(ctx context.Context, shard, username, password string, ttl int) error {
req := keystore.OpenRequest{
Username: username,
Password: password,
TTL: ttl,
}
var resp keystore.OpenResponse
return clientutil.DoJSONHTTPRequest(ctx, c.Client, c.backendURL+"/api/open", &req, &resp)
return clientutil.DoJSONHTTPRequest(ctx, c.be.Client(shard), c.be.URL(shard)+"/api/open", &req, &resp)
}
func (c *Client) Get(ctx context.Context, username, ssoTicket string) ([]byte, error) {
func (c *ksClient) Get(ctx context.Context, shard, username, ssoTicket string) ([]byte, error) {
req := keystore.GetRequest{
Username: username,
SSOTicket: ssoTicket,
}
var resp keystore.GetResponse
err := clientutil.DoJSONHTTPRequest(ctx, c.Client, c.backendURL+"/api/get", &req, &resp)
err := clientutil.DoJSONHTTPRequest(ctx, c.be.Client(shard), c.be.URL(shard)+"/api/get", &req, &resp)
return resp.Key, err
}
func (c *Client) Close(ctx context.Context, username string) error {
func (c *ksClient) Close(ctx context.Context, shard, username string) error {
req := keystore.CloseRequest{
Username: username,
}
var resp keystore.CloseResponse
return clientutil.DoJSONHTTPRequest(ctx, c.Client, c.backendURL+"/api/close", &req, &resp)
return clientutil.DoJSONHTTPRequest(ctx, c.be.Client(shard), c.be.URL(shard)+"/api/close", &req, &resp)
}
......@@ -9,10 +9,10 @@
"revisionTime": "2017-12-16T15:39:23Z"
},
{
"checksumSHA1": "2X2UMundICtpGTb8pTdBk7PCKss=",
"checksumSHA1": "o+rWKVQIDy79ZwrItwa5/whAL6g=",
"path": "git.autistici.org/ai3/go-common/clientutil",
"revision": "8cedcb1d73128f5566216cb3e39ad1ccea318213",
"revisionTime": "2017-12-16T15:39:23Z"
"revision": "9b20acad90c411c48f7ddc837a35ef3d0d6f98d4",
"revisionTime": "2017-12-17T20:32:41Z"
},
{
"checksumSHA1": "wY0SM35qAhX3P2IZzDnYa068cPw=",
......@@ -39,10 +39,10 @@
"revisionTime": "2017-12-15T13:50:57Z"
},
{
"checksumSHA1": "HGK52MX+2CEKVzb9I5y1BfgDkWQ=",
"checksumSHA1": "MgtHklQMI/3fNcZZzkg+fmQUrCQ=",
"path": "git.autistici.org/id/keystore/client",
"revision": "b09f1210471f6a60402e8ced4783be3889a4074f",
"revisionTime": "2017-12-15T13:50:57Z"
"revision": "ae260514708a9eb3e81b554e1b7b2c65ef802584",
"revisionTime": "2017-12-17T21:51:41Z"
},
{
"checksumSHA1": "usT4LCSQItkFvFOQT7cBlkCuGaE=",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment