barton

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2021 License: MIT Imports: 17 Imported by: 0

README

codecov

What is Barton?

Barton is a library written in Golang, with a set of utility functions to help developer create a web API service in Intranet or Internet, with a set of support features properly pre-configured. The features include:

The goal of Barton is to provide an "battery included" tool for developers, who want to focus on their own business logic, without the needs of spending time on library exploration or customization. To meet this goal, Barton intentionally builds itself upon a set of well known Open Source projects, and exposes only a limited configuration options.

Barton is not designed to be a "framework" with maximized flexibility. For example, Barton selects Zerolog as structural logging infrastructure. It does not offer any adoption layer for other famous logging libraries such as Zap or Logrus, or even standard log module. It also pre-configures options for Zerolog, which forces always writting INFO leve logs no matter under development mode or production.

This design decision is not a preferred way for some developers, who want to adopt their favorite libraries as much as possible. Barton chooses an carefully chosen set of dependencies, which keep a minimal warpper layer and a clean codebase. I believe it makes Barton code easy to understand and optimize. For developers who really disagree Barton's dependency selection or design choice, just go ahead to fork the code and modify as you wish.

How to use Barton

Barton is built with Golang 1.16 with Go module. It can be referenced by command below.

go get -u https://github.com/fuzhouch/barton

Quick start

Let's start from a complete example by building a web service and a client which can talk to each other.

The web server listens http://127.0.0.1:8080 and provides two endpoints. The first endpoint, /login to take an HTTP basic login request, and then return an JWT token. The second endpoint, /v1/hello, is protected by JWT authentication, accepts only request with a valid JWT token attached in HTTP request. If the token is good, it returns a string "hello!". If the token is invalid or expired, it returns an error message with HTTP status code set to 400.

The following code demonstrate how to create a simple Echo web app with Barton APIs. You may notice Echo web app is still functioning when the Zerolog and JWT configurations are not set. When NewZerologConfig is not called, the created Echo web app writes structural logs to standard error. Note that Prometheus integration is already enabled and exposed under /metrics path.

The full code source and project files are available at _example/ folder under https://github.com/fuzhouch/barton project.

// _example/server/main.go
package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/fuzhouch/barton"
	"github.com/labstack/echo/v4"
	"github.com/shaj13/go-guardian/v2/auth"
	"github.com/shaj13/go-guardian/v2/auth/strategies/basic"
)

func validate(ctx context.Context, r *http.Request,
	userName, password string) (auth.Info, error) {

	// here connect to db or any other service to fetch user and validate it.
	if userName == "testuser" && password == "testpwd" {
		return auth.NewDefaultUser("testuser",
			"testpwd",
			nil,
			nil), nil
	}
	return nil, fmt.Errorf("Invalid credentials")
}

func main() {
	// Setup Zerolog
	zc := barton.NewZerologConfig().UseUTCTime()
	zc.SetGlobalPolicy().SetGlobalLogger()

	// Setup JWT authentication
	testKey := []byte("keep-it-secret")

	jwtGen := barton.NewHMACJWTGen(testKey)
	// Authentication method
	strategy := basic.New(validate)
	policy := barton.NewJWTGenPolicy(strategy).PrintAuthFailLog(true)

	// Create Echo app with Prometheus enabled.
	// JWT token authentication is enabled explicitly.
	e, cleanup := barton.NewWebApp("MyAPI").NewEcho()
	defer cleanup()

	// Add /login endpoint to handle login requests. It's
	// unprotected.
	e.POST("/login", jwtGen.NewEchoLoginHandler(policy))

	// All other APIs, are protected by JWT checking middleware.
	g := e.Group("/v1", jwtGen.NewEchoMiddleware())
	g.GET("/hello", func(c echo.Context) error { // Protected by JWT.
		return c.String(http.StatusOK, "hello!")
	})

	// The logic is copied from
	// https://github.com/labstack/echo#example
	e.Logger.Fatal(e.Start(":8080"))
}

Starting from Barton v0.3.0, a sub-module called cli is introduced to support creation of command line client. It provides a RootCLI and an HTTPBasicLogin to create Cobra based command line processor. The HTTPBasicLogin provides subcommand to authenticate with remote server, and save received JWT token in configuration file. A full client code looks like below:

// _example/client/main.go
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"

	"github.com/fuzhouch/barton"
	"github.com/fuzhouch/barton/cli"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

func main() {
	// Setup Zerolog
	zc := barton.NewZerologConfig().UseUTCTime()
	zc.SetGlobalPolicy().SetOffGlobalLogger()

	login := cli.NewHTTPBasicLogin("login", "http://127.0.0.1:8080/login")

	rootCLI, cleanup := cli.NewRootCLI("testcli").
		SetLocalViperPolicy().
		AddSubcommand(login).
		NewCobraE(func(c *cobra.Command, args []string) error {
			// IMPORTANT This is an example to show usage of Barton
			// APIs. For showing the main path it skips all error
			// handling logic. This is bad practice for production
			// use. Please properly handle errors instead of copy
			// and paste code blindly.
			token := viper.GetString("testcli.login.token")
			req, _ := http.NewRequest("GET", "http://127.0.0.1:8080/v1/hello", nil)
			req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))

			cli := &http.Client{}
			resp, _ := cli.Do(req)
			defer resp.Body.Close()

			answer, _ := ioutil.ReadAll(resp.Body)
			fmt.Printf("Answer from server: %s\n", answer)

			return nil
		})
	defer cleanup()

	err := rootCLI.Execute()
	if err != nil {
		fmt.Printf("Error on execution: %s.", err.Error())
		os.Exit(1)
	}
}

Please check unit test code to get detailed explanation of APIs and features.

Changelog

v0.3.0
  • Default login sub-command based on github.com/spf13/cobra
  • Username/password basic HTTP login as command line options.
  • URL as command line options.
  • Subcommands support configuration file, following XDG.
  • Default username/token configuration section in configuration file.
  • Built-in login subcommand to send request and receive token.
  • Type renaming to remove golint errors.
v0.2.0
  • A login handler based on https://github.com/shaj13/go-guardian.
  • Support basic-auth login. See UT as demonstration.
  • Support form-based login. Provide a go-guardian's Strategy interface.
  • Customizable log-line on JWT generation and authentication failure.
  • Prometheus counter for JWT generation and authentication failure.
v0.1.0
  • Create Echo web service.
  • A default Prometheus exporter to export service status.
  • Write structural log via Zerolog.
  • Echo app integrate with Zerolog.
  • Utility function to set default Zerolog global settings.
  • Timestamp in logs can be RFC3339 UTC or local time (with zone info).
  • Optionally enabled HMAC JWT token validation.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type FormAuth added in v0.3.0

type FormAuth struct {
	// contains filtered or unexported fields
}

FormAuth creates a cofig object to configure authentication strategy.

func NewFormAuth added in v0.3.0

func NewFormAuth() *FormAuth

NewFormAuth creates an object that configures form authentication.

func (*FormAuth) NewGuardianStrategy added in v0.3.0

func (c *FormAuth) NewGuardianStrategy(fn b.AuthenticateFunc) a.Strategy

NewGuardianStrategy method provides a go-guardian to accept username and password from HTTP form body, uesful for web page authentication. The returned strategy does not support go-guardian's cache strategy for now. As an alternative solution, manipulate cache in basic.AuthenticateFunc.

func (*FormAuth) PasswordKey added in v0.3.0

func (c *FormAuth) PasswordKey(key string) *FormAuth

PasswordKey is control option for NewGuardianFormAuthStrategy() creator, to specify form key name for retriving password.

func (*FormAuth) UsernameKey added in v0.3.0

func (c *FormAuth) UsernameKey(key string) *FormAuth

UsernameKey is control option for NewGuardianFormAuthStrategy() creator, to specify form key name for retriving username.

type HMACJWTGen added in v0.3.0

type HMACJWTGen struct {
	// contains filtered or unexported fields
}

HMACJWTGen provides JWT generation logic with symmetric encryption. By default it supports HS256, HS384 and HS512.

func NewHMACJWTGen added in v0.3.0

func NewHMACJWTGen(signingKey []byte) *HMACJWTGen

NewHMACJWTGen creates a new configuration object to generate JWT token handler and middleware.

func (*HMACJWTGen) ContextKey added in v0.3.0

func (hc *HMACJWTGen) ContextKey(keyName string) *HMACJWTGen

ContextKey specifies the key name we use to lookup token object in echo's Context object.

func (*HMACJWTGen) NewEchoLoginHandler added in v0.3.0

func (hc *HMACJWTGen) NewEchoLoginHandler(p *JWTGenPolicy,
	handlerIdentifier ...string) echo.HandlerFunc

NewEchoLoginHandler create an Labstack Echo framework handler. It takes parameter p, an JWT generator policy object, and a handlerIdentifier string to distinguish this handler when creating Prometheus counters.

func (*HMACJWTGen) NewEchoMiddleware added in v0.3.0

func (hc *HMACJWTGen) NewEchoMiddleware() echo.MiddlewareFunc

NewEchoMiddleware returns a token validation middleware for Labstack Echo framework.

func (*HMACJWTGen) SigningKey added in v0.3.0

func (hc *HMACJWTGen) SigningKey(secret []byte) *HMACJWTGen

SigningKey specifies signing key for JWT signing. The given secret should not be shared with anyone.

func (*HMACJWTGen) SigningMethod added in v0.3.0

func (hc *HMACJWTGen) SigningMethod(method string) *HMACJWTGen

SigningMethod specifies signing method. Supposed method is HS256, HS384 and HS512.

type JWTGenPolicy added in v0.2.0

type JWTGenPolicy struct {
	// contains filtered or unexported fields
}

JWTGenPolicy is a cofiguration we use to control how we generate JWT tokens. It specifies behavior such as expiration time and login approaches.

JWTGenPolicy is designed as a separated configuration object, instead of being a part of HMACJWTConfig. This is to ensure we leave future flexibility when Barton supports JWT with public/private keys.

func NewJWTGenPolicy added in v0.2.0

func NewJWTGenPolicy(strategy auth.Strategy) *JWTGenPolicy

NewJWTGenPolicy generate a new policy configuration. It specifies behaviors like token expiration time and authentication methods. The policy is passed to HMACJWTConfig.NewEchoLoginHandler() method to generate an Echo handler function.

func (*JWTGenPolicy) AuthFailLogMsg added in v0.2.0

func (p *JWTGenPolicy) AuthFailLogMsg(msg string) *JWTGenPolicy

AuthFailLogMsg specifies a log line string when authentication check fails. This message is designed to use when developers search failure message from ElasticSearch or Splunk.

func (*JWTGenPolicy) ExpireSpan added in v0.2.0

func (p *JWTGenPolicy) ExpireSpan(expire time.Duration) *JWTGenPolicy

ExpireSpan specifies a expire time duration.

func (*JWTGenPolicy) PrintAuthFailLog added in v0.2.0

func (p *JWTGenPolicy) PrintAuthFailLog(enable bool) *JWTGenPolicy

PrintAuthFailLog specifies whether login handler writes log line on a failed authentication step. By default it's set to false. Although log line is useful for debugging, it can cause log flooding and eat up disk space of log server, when a malform client intentially generate many bad requests. This is especially true in a cost sensitive deployment.

It's recommended to enable PrintAuthFailLog in development mode, then disable it in production mode.

func (*JWTGenPolicy) TokenIssuedLogMsg added in v0.2.0

func (p *JWTGenPolicy) TokenIssuedLogMsg(msg string) *JWTGenPolicy

TokenIssuedLogMsg specifies a log line string when a token is genearted successfully. This message is designed to use when developers search failure message from ElasticSearch or Splunk.

type TokenResponseBody added in v0.3.0

type TokenResponseBody struct {
	Token  string `json:"jwt"`
	Expire int64  `json:"expire_unix_epoch"`
}

TokenResponseBody represents a structure that returned JSON when trying to login JWT token.

type WebApp added in v0.3.0

type WebApp struct {
	// contains filtered or unexported fields
}

WebApp is a configuration object that sets configurations, and build an Echo web server via NewEcho() method.

func NewWebApp added in v0.3.0

func NewWebApp(appName string) *WebApp

NewWebApp is main entry to start building an Echo app engine. It returns a chainable configuration object, WebApp, which is configured as setter functions. The final step is it calls New() function to really build an Echo engine, plus a cleanup function returned.

func (*WebApp) Name added in v0.3.0

func (c *WebApp) Name(name string) *WebApp

Name setter sets app name for Echo engine. By defualt the name is set to Barton-Echo-App.

func (*WebApp) NewEcho added in v0.3.0

func (c *WebApp) NewEcho() (*echo.Echo, func())

NewEcho method creates a real echo.Echo object, plus a cleanup() function to execute internal cleanup logic, such as unregistering Prometheus metrics collector in global registry.

type ZerologConfig

type ZerologConfig struct {
	// contains filtered or unexported fields
}

ZerologConfig creates an object to configure and set global Zerolog object.

func NewZerologConfig

func NewZerologConfig() *ZerologConfig

NewZerologConfig creates a new config object with default settings: timestamp written to RFC3339 format, and write to os.Stderr.

func (*ZerologConfig) SetGlobalLogger

func (c *ZerologConfig) SetGlobalLogger()

SetGlobalLogger creates a logger object and assign it to global Zerolog object (log.Logger).

func (*ZerologConfig) SetGlobalPolicy

func (c *ZerologConfig) SetGlobalPolicy() *ZerologConfig

SetGlobalPolicy sets default zerolog settings used by Gregson. The following policy are enforced:

1. Always use RFC3339 format ("2006-01-02T15:04:05Z07:00") 2. Timestamp returns UTC. 3. Prints only INFO level logs or above. 4. Sampling is disabled.

#1 and #2 are for readability reason, considering develpers may have micro-services running in different machines.

Special notes for #3: Log level customization is unrecommended. This is to avoid a practice, that developers may write less log in production, but more in dev, assuming reported issue can be reproduced in-house. This is usually not true for Internet oriented services, because issues are triggerred only under high load.

#4 is set by almost same reason with #3. Sampling sacrifaces diagnose feasibility to get smaller file size. This is usually not worthy in production environment.

func (*ZerologConfig) SetOffGlobalLogger

func (c *ZerologConfig) SetOffGlobalLogger()

SetOffGlobalLogger configures global zerolog to discard messages. This is useful when writing tests, but do not use in production.

func (*ZerologConfig) SetWriter

func (c *ZerologConfig) SetWriter(writer io.Writer) *ZerologConfig

SetWriter sets a writer object (io.Writer) that log lines are written to. Note that the io.Writer needs to be closed by caller side if any. By default, ZerologConfig sets writer to os.Stderr.

func (*ZerologConfig) UseLocalTime

func (c *ZerologConfig) UseLocalTime() *ZerologConfig

UseLocalTime enforces timezone info added to Zerolog. Need to call SetGlobalPolicy() to make it take effect.

func (*ZerologConfig) UseUTCTime

func (c *ZerologConfig) UseUTCTime() *ZerologConfig

UseUTCTime forces timezone info to UTC in Zerolog. Need to call SetGlobalPolicy() to make it take effect.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL