Documentation ¶
Overview ¶
Vangoh is a library designed to easily enable go web servers to be secured using HMAC authentication.
Vangoh stands for Vanilla Go HMAC, and it is just that. It makes use of nothing apart from the Go core libraries to provide a robust and flexible solution for adding HMAC request authentication to a new or pre-existing web stack.
It was designed to implement the HMAC scheme that was popularized by Amazon's AWS as described in detail here - http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
The primary characteristic of this implementation is that the signature is placed In the "Authorization" header, along with the access ID, and a tag for the organization.
Apart from implementing the signature-computing scheme defined by AWS, Vangoh also includes support for multiple different secret key providers within one instance, allowing flexibility in how different users and requests are authenticated. In addition, Vangoh allows for choice in the hashing algorithm that your HMAC implementation uses. The constructors default the algorithm used to SHA256, but it can be easily configured to support any class that implements hash.Hash
Vangoh is designed to fit in with the conventions of the net/http package, meaning that integration with middleware stacks is easy, regardless of other software in use.
Index ¶
- Constants
- Variables
- func AddAuthorizationHeader(vg *Vangoh, r *http.Request, org string, key []byte, secret []byte)
- func AddCustomDateHeader(r *http.Request, headerName string)
- func AddDateHeader(r *http.Request)
- type AuthenticationError
- type CallbackPayload
- type SecretProvider
- type SecretProviderWithCallback
- type Vangoh
- func (vg *Vangoh) AddProvider(org string, skp secretProvider) error
- func (vg *Vangoh) AuthenticateRequest(r *http.Request) *AuthenticationError
- func (vg *Vangoh) ConstructBase64Signature(r *http.Request, secret []byte) string
- func (vg *Vangoh) ConstructSignature(r *http.Request, secret []byte) []byte
- func (vg *Vangoh) CreateSigningString(r *http.Request) string
- func (vg *Vangoh) GetDebug() bool
- func (vg *Vangoh) Handler(h http.Handler) http.Handler
- func (vg *Vangoh) IncludeHeader(headerRegex string) error
- func (vg *Vangoh) NegroniHandler(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
- func (vg *Vangoh) SetAlgorithm(algorithm func() hash.Hash)
- func (vg *Vangoh) SetCustomDateHeader(headerName string)
- func (vg *Vangoh) SetDebug(debug bool)
- func (vg *Vangoh) SetMaxTimeSkew(timeSkew time.Duration)
Constants ¶
const AuthRegex = "^[A-Za-z0-9_]+ [A-Za-z0-9_/+]+:" +
"(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"
Expected regex format of the Authorization signature.
An authorization signature consists of three parts:
Authorization: [ORG] [KEY]:[HMAC_SIGNATURE]
The first component is an organizational tag, which must consist of at least one character, and has a valid character set of alphanumeric characters and underscores.
This should be followed by a single space, and then the key, which also must consist of one or more alphanumeric characters and/or underscores.
The key must be followed by a single colon ':' character, and then the signature, encoded in Base64 (valid characters being all alphanumeric, plus "+", forward slash "/", and equals sign "=" as padding on the end if needed.)
Any leading or trailing whitespace around the header will be trimmed before validation.
Variables ¶
var ErrorAuthHeaderMalformed = &AuthenticationError{ c: http.StatusBadRequest, s: "Authorization header does not match expected format", }
var ErrorAuthHeaderMissing = &AuthenticationError{ c: http.StatusBadRequest, s: "Missing 'Authorization' header", }
var ErrorAuthOrgUnknown = &AuthenticationError{ c: http.StatusBadRequest, s: "Authentication organization is not recognized", }
var ErrorDateHeaderMalformed = &AuthenticationError{ c: http.StatusBadRequest, s: "Date header is not a valid format", }
var ErrorDateHeaderMissing = &AuthenticationError{ c: http.StatusBadRequest, s: "Missing 'Date' header", }
var ErrorDateHeaderTooSkewed = &AuthenticationError{ c: http.StatusForbidden, s: "Date header's value is too old", }
var ErrorHMACSignatureMismatch = &AuthenticationError{ c: http.StatusForbidden, s: "HMAC signature does not match", }
var ErrorInProviderKeyLookup = &AuthenticationError{ c: http.StatusInternalServerError, s: "Unable to look up secret key", }
var ErrorSecretNotFound = &AuthenticationError{ c: http.StatusForbidden, s: "Authentication key is not recognized", }
var SupportedDateFormatNames = []string{ time.RFC822, time.RFC822Z, time.RFC850, time.ANSIC, time.RFC1123, time.RFC1123Z, }
The names of the supported formats for the timestamp in the Date HTTP header. If the timestamp does not match one of these formats, the request will fail the authorization check.
Functions ¶
func AddAuthorizationHeader ¶
func AddCustomDateHeader ¶
func AddDateHeader ¶
Types ¶
type AuthenticationError ¶
type AuthenticationError struct {
// contains filtered or unexported fields
}
func (*AuthenticationError) Error ¶
func (a *AuthenticationError) Error() string
func (*AuthenticationError) StatusCode ¶
func (a *AuthenticationError) StatusCode() int
func (*AuthenticationError) WriteResponse ¶
func (a *AuthenticationError) WriteResponse(w http.ResponseWriter, debug bool)
type CallbackPayload ¶
type CallbackPayload struct {
// contains filtered or unexported fields
}
func (*CallbackPayload) GetPayload ¶
func (c *CallbackPayload) GetPayload() interface{}
func (*CallbackPayload) SetPayload ¶
func (c *CallbackPayload) SetPayload(p interface{})
type SecretProvider ¶
type SecretProvider interface { GetSecret(key []byte) ([]byte, error) // contains filtered or unexported methods }
A SecretProvider only needs to implement the secret lookup method as described above.
type SecretProviderWithCallback ¶
type SecretProviderWithCallback interface { GetSecret(key []byte, cbPayload *CallbackPayload) ([]byte, error) SuccessCallback(r *http.Request, cbPayload *CallbackPayload) // contains filtered or unexported methods }
type Vangoh ¶
type Vangoh struct {
// contains filtered or unexported fields
}
Vangoh is an object that forms the primary point of configuration of the middleware HMAC handler. It allows for the configuration of the hashing function to use, the headers (specified as regexes) to be included in the computed signature, and the mapping between organization tags and the secret key providers associated with them.
func NewSingleProvider ¶
func NewSingleProvider(provider secretProvider) *Vangoh
Creates a new Vangoh instance that supports a single secretProvider. Attempting to add providers with AddProvider will fail with an error.
func (*Vangoh) AddProvider ¶
AddProvider sets the secret provider of a specific organization. If the Vangoh instance was created to use a single provider for all requests, regardless of organization tag, calling AddProvider will fail and return an error. If the organization already has a provider, calling AddProvider will fail and return an error.
By supporting different providers based on org tags, there is the ability to configure authentication sources based on user type or purpose. For instance, if an endpoint is going to be used by both a small set of internal services as well as external users, you could create a different provider for each, as demonstrated below.
Example:
func main() { // Create provider for internal services credentials (not included with Vangoh). internalProvider := providers.NewInMemoryProvider(...) // Create provider for normal user credentials (not included with Vangoh). userProvider := providers.NewDatabaseProvider(...) vg := vangoh.New() _ = vg.AddProvider("INT", internalProvider) _ = vg.AddProvider("API", userProvider) // ... }
In this example, any connections made with the authorization header "INT [userID]:[signature]" will be authenticated against `internalProvider`, and connections with the header "API [userID]:[signature]" will be authenticated against `userProvider`.
func (*Vangoh) AuthenticateRequest ¶
func (vg *Vangoh) AuthenticateRequest(r *http.Request) *AuthenticationError
Checks a request for proper authentication details, returning the relevent error if the request fails this check or nil if the request passes.
func (*Vangoh) ConstructBase64Signature ¶
func (*Vangoh) ConstructSignature ¶
func (*Vangoh) CreateSigningString ¶
CreateSigningString creates the string used for signature generation, in accordance with the specifications as laid out in the package documentation. Refer there for more detail, or to the Amazon Signature V2 documentation: http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html.
func (*Vangoh) Handler ¶
Protect a `http.Handler` from unauthenticated requests. The wrapped handler will only be called if the request contains a valid Authorization header.
Example:
func main() { // Create a new Vangoh instance. vg := vangoh.New() // Assuming the endpoint to be protected is called 'baseHandler'. protectedHandler := vg.Handler(unprotectedHandler) // Works just like any other `http.Handler`. http.ListenAndServe("0.0.0.0:3000", protectedHandler) }
func (*Vangoh) IncludeHeader ¶
IncludeHeader specifies additional headers to include in the construction of the HMAC signature body for a request.
Given a regex, any non-canonical (e.g. "X-Aur", not "x-aur") headers that match the regex will be included.
For instance, to match all headers beginning with "X-Aur-", we could include the header regex "X-Aur-.*". It is important to note that this funcationality uses traditional, non-POSIX regular expressions, and will add anchoring to the provided regex if it is not included.
This means that the regex "X-Aur" will only match headers with key "X-Aur" exactly. In order to do prefix matching you must add a wildcard match after, i.e. "X-Aur.*"
func (*Vangoh) NegroniHandler ¶
func (vg *Vangoh) NegroniHandler(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
Implements the `negroni.Handler` interface, for use as a middleware.
Example:
func main() { mux := http.NewServeMux() // Create a new Vangoh instance. vg := vangoh.New() // Create a new Negroni instance, with the standard Recovery and Logger // middlewares. n := negroni.New( negroni.NewRecovery(), negroni.NewLogger(), negroni.HandlerFunc(vg.NegroniHandler)) // Run the app. n.UseHandler(mux) n.Run(":3000") }
func (*Vangoh) SetAlgorithm ¶
func (*Vangoh) SetCustomDateHeader ¶
func (*Vangoh) SetMaxTimeSkew ¶
SetMaxTimeSkew sets the maximum allowable duration between the date and time specified in the Date header and the server time when the response is processed. If the date in the header exceeds the duration Vangoh will respond to the request with a HTTP status 403 Forbidden.
To match the behavior of AWS (15 minute skew window):
vg := vangoh.New() vg.SetMaxTimeSkew(time.Minute * 15)
When checking the date header, Vangoh follows the precedent of RFC 2616, accepting dates in any of the following formats:
ANSIC = "Mon Jan _2 15:04:05 2006" RFC822 = "02 Jan 06 15:04 MST" RFC822Z = "02 Jan 06 15:04 -0700" RFC850 = "Monday, 02-Jan-06 15:04:05 MST" RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700"