Version: 2.2.8
Traefik comes with a lot of plugins by default, but some of our personal needs may not be supported by native plugins, so you need to develop your own plugins. Traefik does not support plugins prior to version 2.3, so we need to modify the source code to add plugins.
Let’s add a token validation plug-in as a demonstration.
The plugin takes the token that the request added to the header, and then asks the backend service to verify that the token is correct.
Code changes
There are three things we need to change,
Add plug-in execution files
Add the plugin’s main logic file to the PKG/Middleware/Auth folder, which can be changed to suit your needs.
package auth
import (
"context"
"encoding/json"
"fmt"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/middlewares"
"github.com/containous/traefik/v2/pkg/tracing"
"github.com/opentracing/opentracing-go/ext"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
const (
tokenTypeName = "TokenAuthType"
)
type tokenAuth struct {
address string
next http.Handler
name string
client http.Client
}
type commonResponse struct {
Status int32 `json:"status"`
Message string `json:"message"`
}
// NewToken creates a passport auth middleware.
func NewToken(ctx context.Context, next http.Handler, config dynamic.TokenAuth, name string) (http.Handler, error) {
log.FromContext(middlewares.GetLoggerCtx(ctx, name, tokenTypeName)).Debug("Creating middleware")
// Plug-in structure
ta := &tokenAuth{
address: config.Address,
next: next,
name: name,
}
// Create an HTTP client that requests other services
ta.client = http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Timeout: 30 * time.Second,
}
return ta, nil
}
func (ta *tokenAuth) GetTracingInformation(a) (string, ext.SpanKindEnum) {
return ta.name, ext.SpanKindRPCClientEnum
}
func (ta tokenAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
logger := log.FromContext(middlewares.GetLoggerCtx(req.Context(), ta.name, tokenTypeName))
errorMsg := []byte("{\"code\":10000, "message\":\" Token verification failed! \ "}")
// Get the token from the header
token := req.Header.Get("token")
if token == "" {
logMessage := fmt.Sprintf("Error calling %s. Cause token is empty", ta.address)
traceAndResponseDebug(logger, rw, req, logMessage, []byte("{\"statue\":10000,\"message\":\"token is empty\"}"), http.StatusBadRequest)
return
}
// All of the following are requests for other services to validate tokens
// Build the request body
form := url.Values{}
form.Add("token", token)
passportReq, err := http.NewRequest(http.MethodPost, ta.address, strings.NewReader(form.Encode()))
tracing.LogRequest(tracing.GetSpan(req), passportReq)
iferr ! =nil {
logMessage := fmt.Sprintf("Error calling %s. Cause %s", ta.address, err)
traceAndResponseDebug(logger, rw, req, logMessage, errorMsg, http.StatusBadRequest)
return
}
tracing.InjectRequestHeaders(req)
passportReq.Header.Set("Content-Type"."application/x-www-form-urlencoded")
/ / post request
passportResponse, forwardErr := ta.client.Do(passportReq)
ifforwardErr ! =nil {
logMessage := fmt.Sprintf("Error calling %s. Cause: %s", ta.address, forwardErr)
traceAndResponseError(logger, rw, req, logMessage, errorMsg, http.StatusBadRequest)
return
}
logger.Info(fmt.Sprintf("Passport auth calling %s. Response: %+v", ta.address, passportResponse))
/ / read the body
body, readError := ioutil.ReadAll(passportResponse.Body)
ifreadError ! =nil {
logMessage := fmt.Sprintf("Error reading body %s. Cause: %s", ta.address, readError)
traceAndResponseError(logger, rw, req, logMessage, errorMsg, http.StatusBadRequest)
return
}
defer passportResponse.Body.Close()
ifpassportResponse.StatusCode ! = http.StatusOK { logMessage := fmt.Sprintf("Remote error %s. StatusCode: %d", ta.address, passportResponse.StatusCode)
traceAndResponseDebug(logger, rw, req, logMessage, errorMsg, http.StatusBadRequest)
return
}
/ / body
var commonRes commonResponse
err = json.Unmarshal(body, &commonRes)
iferr ! =nil {
logMessage := fmt.Sprintf("Body unmarshal error. Body: %s", body)
traceAndResponseError(logger, rw, req, logMessage, errorMsg, http.StatusBadRequest)
return
}
// Check the return value. Non-0 indicates validation failure
ifcommonRes.Status ! =0 {
logMessage := fmt.Sprintf("Body status is not success. Status: %d", commonRes.Status)
traceAndResponseDebug(logger, rw, req, logMessage, errorMsg, http.StatusBadRequest)
return
}
ta.next.ServeHTTP(rw, req)
}
func traceAndResponseDebug(logger log.Logger, rw http.ResponseWriter, req *http.Request, logMessage string, errorMsg []byte, status int) {
logger.Debug(logMessage)
tracing.SetErrorWithEvent(req, logMessage)
rw.Header().Set("Content-Type"."application/json; charset=UTF-8")
rw.WriteHeader(status)
_, _ = rw.Write(errorMsg)
}
func traceAndResponseInfo(logger log.Logger, rw http.ResponseWriter, req *http.Request, logMessage string, errorMsg []byte, status int) {
logger.Info(logMessage)
tracing.SetErrorWithEvent(req, logMessage)
rw.Header().Set("Content-Type"."application/json; charset=UTF-8")
rw.WriteHeader(status)
_, _ = rw.Write(errorMsg)
}
func traceAndResponseError(logger log.Logger, rw http.ResponseWriter, req *http.Request, logMessage string, errorMsg []byte, status int) {
logger.Debug(logMessage)
tracing.SetErrorWithEvent(req, logMessage)
rw.Header().Set("Content-Type"."application/json; charset=UTF-8")
rw.WriteHeader(status)
_, _ = rw.Write(errorMsg)
}
Copy the code
Add a dynamic configuration mapping
Add the mapping between the configuration file and the entity here
// pkg/config/dynamic/middlewares.go
package dynamic
/ *... * /
// Middleware holds the Middleware configuration.
type Middleware struct {
/ *... * /
//
TokenAuth *TokenAuth `json:"tokenAuth,omitempty" toml:"tokenAuth,omitempty" yaml:"tokenAuth,omitempty"`
}
/ *... * /
// TokenAuth
type TokenAuth struct {
Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
}
Copy the code
Constructing a plug-in example
Here is the code to create the plug-in entity
// pkg/server/middleware/middlewares.go
func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (alice.Constructor, error) {
/ *... * /
// TokenAuth
ifconfig.TokenAuth ! =nil {
ifmiddleware ! =nil {
return nil, badConf
}
middleware = func(next http.Handler) (http.Handler, error) {
return auth.NewToken(ctx, next, *config.TokenAuth, middlewareName)
}
}
/ *... * /
}
Copy the code
Packaging configuration
Use your own package command to package Linux (docker installation required).
make binary
Copy the code
The executable file is then generated under the dist folder.
Adding plug-in Configuration
HTTP: middlewares: # token validation token-auth: tokenAuth: address: <http://xxx.xxx.com/token_info>Copy the code
Added dynamic route configuration
http:
routers:
svc:
entryPoints:
- web
middlewares:
- token-auth
service: svc
rule: PathPrefix(`/list`)
Copy the code
This will make the newly added plug-in available.