bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/pkg/apiserver/middlewares/v1/jwt.go (about)

     1  package v1
     2  
     3  import (
     4  	"crypto/rand"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"errors"
    11  
    12  	jwt "github.com/appleboy/gin-jwt/v2"
    13  	"bitbucket.org/Aishee/synsec/pkg/database"
    14  	"bitbucket.org/Aishee/synsec/pkg/database/ent/machine"
    15  	"bitbucket.org/Aishee/synsec/pkg/models"
    16  	"bitbucket.org/Aishee/synsec/pkg/types"
    17  	"github.com/gin-gonic/gin"
    18  	"github.com/go-openapi/strfmt"
    19  	log "github.com/sirupsen/logrus"
    20  	"golang.org/x/crypto/bcrypt"
    21  )
    22  
    23  var identityKey = "id"
    24  
    25  type JWT struct {
    26  	Middleware *jwt.GinJWTMiddleware
    27  	DbClient   *database.Client
    28  }
    29  
    30  func PayloadFunc(data interface{}) jwt.MapClaims {
    31  	if value, ok := data.(*models.WatcherAuthRequest); ok {
    32  		return jwt.MapClaims{
    33  			identityKey: &value.MachineID,
    34  		}
    35  	}
    36  	return jwt.MapClaims{}
    37  }
    38  
    39  func IdentityHandler(c *gin.Context) interface{} {
    40  	claims := jwt.ExtractClaims(c)
    41  	machineId := claims[identityKey].(string)
    42  	return &models.WatcherAuthRequest{
    43  		MachineID: &machineId,
    44  	}
    45  }
    46  
    47  func (j *JWT) Authenticator(c *gin.Context) (interface{}, error) {
    48  	defer types.CatchPanic("synsec/middlewaresV1/jwt/Authenticator")
    49  	var loginInput models.WatcherAuthRequest
    50  	var scenarios string
    51  	var err error
    52  	if err := c.ShouldBindJSON(&loginInput); err != nil {
    53  		return "", errors.New(fmt.Sprintf("missing : %v", err.Error()))
    54  	}
    55  	if err := loginInput.Validate(strfmt.Default); err != nil {
    56  		return "", errors.New("input format error")
    57  	}
    58  	machineID := *loginInput.MachineID
    59  	password := *loginInput.Password
    60  	scenariosInput := loginInput.Scenarios
    61  
    62  	machine, err := j.DbClient.Ent.Machine.Query().
    63  		Where(machine.MachineId(machineID)).
    64  		First(j.DbClient.CTX)
    65  	if err != nil {
    66  		log.Printf("Error machine login for %s : %+v ", machineID, err)
    67  		return nil, err
    68  	}
    69  
    70  	if machine == nil {
    71  		log.Errorf("Nothing for '%s'", machineID)
    72  		return nil, jwt.ErrFailedAuthentication
    73  	}
    74  
    75  	if !machine.IsValidated {
    76  		return nil, fmt.Errorf("machine %s not validated", machineID)
    77  	}
    78  
    79  	if err = bcrypt.CompareHashAndPassword([]byte(machine.Password), []byte(password)); err != nil {
    80  		return nil, jwt.ErrFailedAuthentication
    81  	}
    82  
    83  	if len(scenariosInput) > 0 {
    84  		for _, scenario := range scenariosInput {
    85  			if scenarios == "" {
    86  				scenarios = scenario
    87  			} else {
    88  				scenarios += "," + scenario
    89  			}
    90  		}
    91  		err = j.DbClient.UpdateMachineScenarios(scenarios, machine.ID)
    92  		if err != nil {
    93  			log.Errorf("Failed to update scenarios list for '%s': %s\n", machineID, err)
    94  			return nil, jwt.ErrFailedAuthentication
    95  		}
    96  	}
    97  
    98  	if machine.IpAddress == "" {
    99  		err = j.DbClient.UpdateMachineIP(c.ClientIP(), machine.ID)
   100  		if err != nil {
   101  			log.Errorf("Failed to update ip address for '%s': %s\n", machineID, err)
   102  			return nil, jwt.ErrFailedAuthentication
   103  		}
   104  	}
   105  
   106  	if machine.IpAddress != c.ClientIP() && machine.IpAddress != "" {
   107  		log.Warningf("new IP address detected for machine '%s': %s (old: %s)", machine.MachineId, c.ClientIP(), machine.IpAddress)
   108  		err = j.DbClient.UpdateMachineIP(c.ClientIP(), machine.ID)
   109  		if err != nil {
   110  			log.Errorf("Failed to update ip address for '%s': %s\n", machine.MachineId, err)
   111  			return nil, jwt.ErrFailedAuthentication
   112  		}
   113  	}
   114  
   115  	useragent := strings.Split(c.Request.UserAgent(), "/")
   116  	if len(useragent) != 2 {
   117  		log.Warningf("bad user agent '%s' from '%s'", c.Request.UserAgent(), c.ClientIP())
   118  		return nil, jwt.ErrFailedAuthentication
   119  	}
   120  
   121  	if err := j.DbClient.UpdateMachineVersion(useragent[1], machine.ID); err != nil {
   122  		log.Errorf("unable to update machine '%s' version '%s': %s", machine.MachineId, useragent[1], err)
   123  		log.Errorf("bad user agent from : %s", c.ClientIP())
   124  		return nil, jwt.ErrFailedAuthentication
   125  	}
   126  
   127  	return &models.WatcherAuthRequest{
   128  		MachineID: &machineID,
   129  	}, nil
   130  
   131  }
   132  
   133  func Authorizator(data interface{}, c *gin.Context) bool {
   134  	return true
   135  }
   136  
   137  func Unauthorized(c *gin.Context, code int, message string) {
   138  	c.JSON(code, gin.H{
   139  		"code":    code,
   140  		"message": message,
   141  	})
   142  }
   143  
   144  func NewJWT(dbClient *database.Client) (*JWT, error) {
   145  	// Get secret from environment variable "SECRET"
   146  	var (
   147  		secret []byte
   148  	)
   149  
   150  	//Please be aware that brute force HS256 is possible.
   151  	//PLEASE choose a STRONG secret
   152  	secret_string := os.Getenv("CS_LAPI_SECRET")
   153  	if secret_string == "" {
   154  		secret = make([]byte, 64)
   155  		if n, err := rand.Read(secret); err != nil {
   156  			log.Fatalf("unable to generate a new random seed for JWT generation")
   157  		} else {
   158  			if n != 64 {
   159  				log.Fatalf("not enough entropy at random seed generation for JWT generation")
   160  			}
   161  		}
   162  	} else {
   163  		secret = []byte(secret_string)
   164  		if len(secret) < 64 {
   165  			log.Fatalf("secret not strong enough")
   166  		}
   167  	}
   168  
   169  	jwtMiddleware := &JWT{
   170  		DbClient: dbClient,
   171  	}
   172  
   173  	ret, err := jwt.New(&jwt.GinJWTMiddleware{
   174  		Realm:           "Synsec API local",
   175  		Key:             secret,
   176  		Timeout:         time.Hour,
   177  		MaxRefresh:      time.Hour,
   178  		IdentityKey:     identityKey,
   179  		PayloadFunc:     PayloadFunc,
   180  		IdentityHandler: IdentityHandler,
   181  		Authenticator:   jwtMiddleware.Authenticator,
   182  		Authorizator:    Authorizator,
   183  		Unauthorized:    Unauthorized,
   184  		TokenLookup:     "header: Authorization, query: token, cookie: jwt",
   185  		TokenHeadName:   "Bearer",
   186  		TimeFunc:        time.Now,
   187  	})
   188  
   189  	errInit := ret.MiddlewareInit()
   190  	if errInit != nil {
   191  		return &JWT{}, fmt.Errorf("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
   192  	}
   193  
   194  	if err != nil {
   195  		return &JWT{}, err
   196  	}
   197  
   198  	return &JWT{Middleware: ret}, nil
   199  }