github.com/wostzone/hub/auth@v0.0.0-20220118060317-7bb375743b17/pkg/authservice/AuthService.go (about)

     1  // Package authservice to serve a REST api for authentication and token refresh
     2  package authservice
     3  
     4  import (
     5  	"crypto/ecdsa"
     6  	"crypto/tls"
     7  	"crypto/x509"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  
    12  	"github.com/gorilla/mux"
    13  	"github.com/sirupsen/logrus"
    14  	"github.com/wostzone/hub/auth/pkg/authenticate"
    15  	"github.com/wostzone/hub/auth/pkg/configstore"
    16  	"github.com/wostzone/hub/auth/pkg/unpwstore"
    17  	"github.com/wostzone/hub/lib/client/pkg/tlsclient"
    18  	"github.com/wostzone/hub/lib/serve/pkg/tlsserver"
    19  )
    20  
    21  // PluginID of the service
    22  const PluginID = "authservice"
    23  
    24  // DefaultAuthServicePort to connect to the auth service
    25  const DefaultAuthServicePort = 8881
    26  
    27  // internal constant for appID route parameter
    28  const appIDParam = "appid"
    29  
    30  // AuthServiceConfig contains the service configuration
    31  type AuthServiceConfig struct {
    32  	// Override of the server address from hub.yaml
    33  	Address string `yaml:"address"`
    34  
    35  	// Service listening port or 0 to use the default port 8881
    36  	Port uint `yaml:"port"`
    37  
    38  	// ClientID to identify this service as. Default is the pluginID
    39  	ClientID string `yaml:"clientID"`
    40  
    41  	// Enable the configuration store for authenticated users. A folder MUST be set.
    42  	ConfigStoreEnabled bool `yaml:"configStoreEnabled"`
    43  
    44  	// Set the config store folder. Required for enabling the config store
    45  	ConfigStoreFolder string `yaml:"configStoreFolder"`
    46  
    47  	// PasswordFile to read from. Use "" for default defined in 'unpwstore.DefaultPasswordFile'
    48  	PasswordFile string `yaml:"passwordFile"`
    49  }
    50  
    51  // AuthService for handling authentication and token refresh requests
    52  //  1. Handle login requests and issue JWT tokens
    53  //  2. Handle refresh requests and re-issue JWT tokens
    54  //  3. Handle config requests to persist client configuration
    55  //
    56  type AuthService struct {
    57  	config        AuthServiceConfig
    58  	configStore   *configstore.ConfigStore
    59  	running       bool
    60  	tlsServer     *tlsserver.TLSServer
    61  	signingKey    *ecdsa.PrivateKey
    62  	authenticator *authenticate.Authenticator
    63  }
    64  
    65  // EnableConfigStore listens for configuration store requests.
    66  // URL is PUT/GET {server}/auth/config/{appID}
    67  //  storeFolder Folder to store user configuration files
    68  func (srv *AuthService) EnableConfigStore(storeFolder string) {
    69  	srv.configStore = configstore.NewConfigStore(storeFolder)
    70  	err := srv.configStore.Open()
    71  	if err != nil {
    72  		logrus.Errorf("Failed opening user configuration store: %s", err)
    73  		return
    74  	}
    75  
    76  	srv.tlsServer.AddHandler(tlsclient.DefaultJWTConfigPath+"/{"+appIDParam+"}", srv.ServeConfig)
    77  }
    78  
    79  // ServeConfig serves or updates user configuration [GET/PUT]
    80  func (srv *AuthService) ServeConfig(userID string, resp http.ResponseWriter, req *http.Request) {
    81  	logrus.Infof("AuthService.ServeConfig: userID=%s", userID)
    82  	appID := mux.Vars(req)[appIDParam]
    83  	if req.Method == http.MethodPut {
    84  		logrus.Warningf("ServeConfig, updated configuration for user '%s'", userID)
    85  		payload, err := ioutil.ReadAll(req.Body)
    86  		if err != nil {
    87  			srv.tlsServer.WriteBadRequest(resp, "Missing payload")
    88  			return
    89  		}
    90  		_ = srv.configStore.Put(userID, appID, string(payload))
    91  	} else if req.Method == http.MethodGet {
    92  		payload := srv.configStore.Get(userID, appID)
    93  		_, _ = resp.Write([]byte(payload))
    94  		return
    95  	} else {
    96  		logrus.Warningf("ServeConfig, method %s not support for user %s", req.Method, userID)
    97  		srv.tlsServer.WriteBadRequest(resp, "Bad method "+req.Method)
    98  		return
    99  	}
   100  }
   101  
   102  // SetPassword for updating a user's password
   103  func (srv *AuthService) SetPassword(userID, password string) error {
   104  	return srv.authenticator.SetPassword(userID, password)
   105  }
   106  
   107  // Start listening for login and refresh requests
   108  func (srv *AuthService) Start() error {
   109  	var err error
   110  
   111  	// call me as often as you like, or is this an error?
   112  	if srv.running {
   113  		err := fmt.Errorf("AuthService is already running")
   114  		logrus.Error(err)
   115  		return err
   116  	}
   117  	pwStore := unpwstore.NewPasswordFileStore(srv.config.PasswordFile, srv.config.ClientID)
   118  	srv.authenticator = authenticate.NewAuthenticator(pwStore)
   119  	err = srv.authenticator.Start()
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	err = srv.tlsServer.Start()
   125  	if err != nil {
   126  		logrus.Errorf("AuthService.Start: Error starting authservice: %s", err)
   127  		return err
   128  	}
   129  	// add auth handlers
   130  	srv.tlsServer.EnableJwtAuth(&srv.signingKey.PublicKey)
   131  	srv.tlsServer.EnableJwtIssuer(srv.signingKey, srv.authenticator.VerifyUsernamePassword)
   132  
   133  	if srv.config.ConfigStoreEnabled && srv.config.ConfigStoreFolder != "" {
   134  		srv.EnableConfigStore(srv.config.ConfigStoreFolder)
   135  	}
   136  	srv.running = true
   137  	return nil
   138  }
   139  
   140  // Stop the authservice
   141  func (srv *AuthService) Stop() {
   142  	if srv.running {
   143  		srv.running = false
   144  		srv.authenticator.Stop()
   145  		srv.tlsServer.Stop()
   146  	}
   147  }
   148  
   149  // NewJwtAuthService creates a new instance of a TLS authservice for JWT authentication.
   150  //
   151  // The signing key contains the key needed for JWT token generation and verification.
   152  // For single signon the server certificate public key can be used for verification if the
   153  // certificates are generated using the same key pair.
   154  //
   155  //  config 		service configuration
   156  //  signingKey  private/public key used to sign and verify JWT tokens. nil to use the server certificate keys.
   157  //  serverCert  server own TLS certificate, signed with ecdsa keys
   158  //  caCert      CA certificate to verify client certificates
   159  //  verifyUsernamePassword function of the password store to verify username/password
   160  // This returns the authentication authservice instance.
   161  func NewJwtAuthService(
   162  	config AuthServiceConfig,
   163  	signingKey *ecdsa.PrivateKey,
   164  	serverCert *tls.Certificate,
   165  	caCert *x509.Certificate) *AuthService {
   166  
   167  	if config.Port == 0 {
   168  		config.Port = DefaultAuthServicePort
   169  	}
   170  	if config.ClientID == "" {
   171  		config.ClientID = PluginID
   172  	}
   173  
   174  	// The TLS server authenticates a request.
   175  	tlsServer := tlsserver.NewTLSServer(config.Address, config.Port, serverCert, caCert)
   176  
   177  	if signingKey == nil {
   178  		signingKey = serverCert.PrivateKey.(*ecdsa.PrivateKey)
   179  	}
   180  
   181  	srv := AuthService{
   182  		config:     config,
   183  		tlsServer:  tlsServer,
   184  		signingKey: signingKey,
   185  	}
   186  	return &srv
   187  }