garm/auth/instance_middleware.go
2022-05-03 19:49:14 +00:00

132 lines
3.2 KiB
Go

package auth
import (
"context"
"fmt"
"net/http"
"runner-manager/config"
dbCommon "runner-manager/database/common"
runnerErrors "runner-manager/errors"
"runner-manager/params"
"runner-manager/runner/common"
"strings"
"time"
"github.com/golang-jwt/jwt"
"github.com/pkg/errors"
)
// InstanceJWTClaims holds JWT claims
type InstanceJWTClaims struct {
ID string `json:"id"`
Name string `json:"name"`
PoolID string `json:"provider_id"`
// Scope is either repository or organization
Scope common.PoolType `json:"scope"`
// Entity is the repo or org name
Entity string `json:"entity"`
jwt.StandardClaims
}
func NewInstanceJWTToken(instance params.Instance, secret, entity string, poolType common.PoolType) (string, error) {
// make TTL configurable?
expireToken := time.Now().Add(3 * time.Hour).Unix()
claims := InstanceJWTClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireToken,
Issuer: "runner-manager",
},
ID: instance.ID,
Name: instance.Name,
PoolID: instance.PoolID,
Scope: poolType,
Entity: entity,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(secret))
if err != nil {
return "", errors.Wrap(err, "signing token")
}
return tokenString, nil
}
// instanceMiddleware is the authentication middleware
// used with gorilla
type instanceMiddleware struct {
store dbCommon.Store
auth *Authenticator
cfg config.JWTAuth
}
// NewjwtMiddleware returns a populated jwtMiddleware
func NewInstanceMiddleware(store dbCommon.Store, cfg config.JWTAuth) (Middleware, error) {
return &instanceMiddleware{
store: store,
cfg: cfg,
}, nil
}
func (amw *instanceMiddleware) claimsToContext(ctx context.Context, claims *InstanceJWTClaims) (context.Context, error) {
if claims == nil {
return ctx, runnerErrors.ErrUnauthorized
}
if claims.Name == "" {
return nil, runnerErrors.ErrUnauthorized
}
instanceInfo, err := amw.store.GetInstanceByName(ctx, claims.Name)
if err != nil {
return ctx, runnerErrors.ErrUnauthorized
}
ctx = PopulateInstanceContext(ctx, instanceInfo)
return ctx, nil
}
// Middleware implements the middleware interface
func (amw *instanceMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO: Log error details when authentication fails
ctx := r.Context()
authorizationHeader := r.Header.Get("authorization")
if authorizationHeader == "" {
invalidAuthResponse(w)
return
}
bearerToken := strings.Split(authorizationHeader, " ")
if len(bearerToken) != 2 {
invalidAuthResponse(w)
return
}
claims := &InstanceJWTClaims{}
token, err := jwt.ParseWithClaims(bearerToken[1], claims, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("invalid signing method")
}
return []byte(amw.cfg.Secret), nil
})
if err != nil {
invalidAuthResponse(w)
return
}
if !token.Valid {
invalidAuthResponse(w)
return
}
ctx, err = amw.claimsToContext(ctx, claims)
if err != nil {
invalidAuthResponse(w)
return
}
// ctx = SetJWTClaim(ctx, *claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}