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

     1  package v1
     2  
     3  import (
     4  	"crypto/sha512"
     5  	"fmt"
     6  	"net/http"
     7  	"strconv"
     8  	"time"
     9  
    10  	"bitbucket.org/Aishee/synsec/pkg/database/ent"
    11  	"bitbucket.org/Aishee/synsec/pkg/models"
    12  	"bitbucket.org/Aishee/synsec/pkg/types"
    13  	"github.com/gin-gonic/gin"
    14  	log "github.com/sirupsen/logrus"
    15  )
    16  
    17  func FormatDecisions(decisions []*ent.Decision) ([]*models.Decision, error) {
    18  	var results []*models.Decision
    19  	for _, dbDecision := range decisions {
    20  		duration := dbDecision.Until.Sub(time.Now()).String()
    21  		decision := models.Decision{
    22  			ID:       int64(dbDecision.ID),
    23  			Duration: &duration,
    24  			Scenario: &dbDecision.Scenario,
    25  			Scope:    &dbDecision.Scope,
    26  			Value:    &dbDecision.Value,
    27  			Type:     &dbDecision.Type,
    28  			Origin:   &dbDecision.Origin,
    29  		}
    30  		results = append(results, &decision)
    31  	}
    32  	return results, nil
    33  }
    34  
    35  func (c *Controller) GetDecision(gctx *gin.Context) {
    36  	defer types.CatchPanic("synsec/controllersV1/GetDecision")
    37  	var err error
    38  	var results []*models.Decision
    39  	var data []*ent.Decision
    40  
    41  	data, err = c.DBClient.QueryDecisionWithFilter(gctx.Request.URL.Query())
    42  	if err != nil {
    43  		c.HandleDBErrors(gctx, err)
    44  		return
    45  	}
    46  
    47  	results, err = FormatDecisions(data)
    48  	if err != nil {
    49  		gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
    50  		return
    51  	}
    52  	/*let's follow a naive logic : when a bouncer queries /decisions, if the answer is empty, we assume there is no decision for this ip/user/...,
    53  	but if it's non-empty, it means that there is one or more decisions for this target*/
    54  	if len(results) > 0 {
    55  		PrometheusBouncersHasNonEmptyDecision(gctx)
    56  	} else {
    57  		PrometheusBouncersHasEmptyDecision(gctx)
    58  	}
    59  
    60  	if gctx.Request.Method == "HEAD" {
    61  		gctx.String(http.StatusOK, "")
    62  		return
    63  	}
    64  	gctx.JSON(http.StatusOK, results)
    65  	return
    66  }
    67  
    68  func (c *Controller) DeleteDecisionById(gctx *gin.Context) {
    69  	defer types.CatchPanic("synsec/controllersV1/DeleteDecisionById")
    70  	var err error
    71  
    72  	decisionIDStr := gctx.Param("decision_id")
    73  	decisionID, err := strconv.Atoi(decisionIDStr)
    74  	if err != nil {
    75  		gctx.JSON(http.StatusBadRequest, gin.H{"message": "decision_id must be valid integer"})
    76  		return
    77  	}
    78  	err = c.DBClient.SoftDeleteDecisionByID(decisionID)
    79  	if err != nil {
    80  		c.HandleDBErrors(gctx, err)
    81  		return
    82  	}
    83  
    84  	deleteDecisionResp := models.DeleteDecisionResponse{
    85  		NbDeleted: "1",
    86  	}
    87  
    88  	gctx.JSON(http.StatusOK, deleteDecisionResp)
    89  	return
    90  }
    91  
    92  func (c *Controller) DeleteDecisions(gctx *gin.Context) {
    93  	defer types.CatchPanic("synsec/controllersV1/DeleteDecisions")
    94  	var err error
    95  
    96  	nbDeleted, err := c.DBClient.SoftDeleteDecisionsWithFilter(gctx.Request.URL.Query())
    97  	if err != nil {
    98  		c.HandleDBErrors(gctx, err)
    99  		return
   100  	}
   101  	deleteDecisionResp := models.DeleteDecisionResponse{
   102  		NbDeleted: nbDeleted,
   103  	}
   104  
   105  	gctx.JSON(http.StatusOK, deleteDecisionResp)
   106  	return
   107  }
   108  
   109  func (c *Controller) StreamDecision(gctx *gin.Context) {
   110  	defer types.CatchPanic("synsec/controllersV1/StreamDecision")
   111  	var data []*ent.Decision
   112  	ret := make(map[string][]*models.Decision, 0)
   113  	ret["new"] = []*models.Decision{}
   114  	ret["deleted"] = []*models.Decision{}
   115  
   116  	val := gctx.Request.Header.Get(c.APIKeyHeader)
   117  	hashedKey := sha512.New()
   118  	hashedKey.Write([]byte(val))
   119  	hashStr := fmt.Sprintf("%x", hashedKey.Sum(nil))
   120  	bouncerInfo, err := c.DBClient.SelectBouncer(hashStr)
   121  	if err != nil {
   122  		if _, ok := err.(*ent.NotFoundError); ok {
   123  			gctx.JSON(http.StatusForbidden, gin.H{"message": err.Error()})
   124  		} else {
   125  			gctx.JSON(http.StatusUnauthorized, gin.H{"message": "not allowed"})
   126  		}
   127  		return
   128  	}
   129  
   130  	if bouncerInfo == nil {
   131  		gctx.JSON(http.StatusUnauthorized, gin.H{"message": "not allowed"})
   132  		return
   133  	}
   134  
   135  	// if the blocker just start, return all decisions
   136  	if val, ok := gctx.Request.URL.Query()["startup"]; ok {
   137  		if val[0] == "true" {
   138  			data, err := c.DBClient.QueryAllDecisions()
   139  			if err != nil {
   140  				log.Errorf("failed querying decisions: %v", err)
   141  				gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   142  				return
   143  			}
   144  			ret["new"], err = FormatDecisions(data)
   145  			if err != nil {
   146  				log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
   147  				gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   148  				return
   149  			}
   150  
   151  			// getting expired decisions
   152  			data, err = c.DBClient.QueryExpiredDecisions()
   153  			if err != nil {
   154  				log.Errorf("unable to query expired decision for '%s' : %v", bouncerInfo.Name, err)
   155  				gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   156  				return
   157  			}
   158  			ret["deleted"], err = FormatDecisions(data)
   159  			if err != nil {
   160  				log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
   161  				gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   162  				return
   163  			}
   164  
   165  			if err := c.DBClient.UpdateBouncerLastPull(time.Now(), bouncerInfo.ID); err != nil {
   166  				log.Errorf("unable to update bouncer '%s' pull: %v", bouncerInfo.Name, err)
   167  				gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   168  				return
   169  			}
   170  			if gctx.Request.Method == "HEAD" {
   171  				gctx.String(http.StatusOK, "")
   172  				return
   173  			}
   174  			gctx.JSON(http.StatusOK, ret)
   175  			return
   176  		}
   177  	}
   178  
   179  	// getting new decisions
   180  	data, err = c.DBClient.QueryNewDecisionsSince(bouncerInfo.LastPull)
   181  	if err != nil {
   182  		log.Errorf("unable to query new decision for '%s' : %v", bouncerInfo.Name, err)
   183  		gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   184  		return
   185  	}
   186  	ret["new"], err = FormatDecisions(data)
   187  	if err != nil {
   188  		log.Errorf("unable to format new decision for '%s' : %v", bouncerInfo.Name, err)
   189  		gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   190  		return
   191  	}
   192  
   193  	// getting expired decisions
   194  	data, err = c.DBClient.QueryExpiredDecisionsSince(bouncerInfo.LastPull.Add((-2 * time.Second))) // do we want to give exactly lastPull time ?
   195  	if err != nil {
   196  		log.Errorf("unable to query expired decision for '%s' : %v", bouncerInfo.Name, err)
   197  		gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   198  		return
   199  	}
   200  	ret["deleted"], err = FormatDecisions(data)
   201  	if err != nil {
   202  		log.Errorf("unable to format expired decision for '%s' : %v", bouncerInfo.Name, err)
   203  		gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   204  		return
   205  	}
   206  
   207  	if err := c.DBClient.UpdateBouncerLastPull(time.Now(), bouncerInfo.ID); err != nil {
   208  		log.Errorf("unable to update bouncer '%s' pull: %v", bouncerInfo.Name, err)
   209  		gctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
   210  		return
   211  	}
   212  
   213  	gctx.JSON(http.StatusOK, ret)
   214  	return
   215  }