Skip to content

Commit

Permalink
GRA-1298: License check changes, free tier limits for saas (#2418)
Browse files Browse the repository at this point in the history
* set free tier limits through config

* add host limit to config

* check for host limit on free tier

* fix license validation, replace node limit with hosts

* add hosts to telemetry data

* debug init

* validate license every 1hr

* hook manager, api to fetch server usage

* hook manager, server usage api

* encode json server usage api

* update ngork url

* update license validation endpoint

* avoid setting limits on eer

* adding hotfix

* correct users limits env var

* add comments to exported funcs

---------

Co-authored-by: afeiszli <alex.feiszli@gmail.com>
  • Loading branch information
abhishek9686 and afeiszli authored Jun 28, 2023
1 parent 8461735 commit 230e062
Show file tree
Hide file tree
Showing 15 changed files with 225 additions and 92 deletions.
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ type ServerConfig struct {
TurnUserName string `yaml:"turn_username"`
TurnPassword string `yaml:"turn_password"`
UseTurn bool `yaml:"use_turn"`
UsersLimit int `yaml:"user_limit"`
ClientsLimit int `yaml:"client_limit"`
NetworksLimit int `yaml:"network_limit"`
HostsLimit int `yaml:"host_limit"`
DeployedByOperator bool `yaml:"deployed_by_operator"`
}

// ProxyMode - default proxy mode for server
Expand Down
10 changes: 1 addition & 9 deletions controllers/limits.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/servercfg"
)

// limit consts
Expand All @@ -23,20 +22,13 @@ func checkFreeTierLimits(limit_choice int, next http.Handler) http.HandlerFunc {
Code: http.StatusForbidden, Message: "free tier limits exceeded on networks",
}

if logic.Free_Tier && servercfg.Is_EE { // check that free tier limits not exceeded
if logic.Free_Tier { // check that free tier limits not exceeded
if limit_choice == networks_l {
currentNetworks, err := logic.GetNetworks()
if (err != nil && !database.IsEmptyRecord(err)) || len(currentNetworks) >= logic.Networks_Limit {
logic.ReturnErrorResponse(w, r, errorResponse)
return
}
} else if limit_choice == node_l {
nodes, err := logic.GetAllNodes()
if (err != nil && !database.IsEmptyRecord(err)) || len(nodes) >= logic.Node_Limit {
errorResponse.Message = "free tier limits exceeded on nodes"
logic.ReturnErrorResponse(w, r, errorResponse)
return
}
} else if limit_choice == users_l {
users, err := logic.GetUsers()
if (err != nil && !database.IsEmptyRecord(err)) || len(users) >= logic.Users_Limit {
Expand Down
38 changes: 38 additions & 0 deletions controllers/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,38 @@ func serverHandlers(r *mux.Router) {
r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods(http.MethodGet)
r.HandleFunc("/api/server/getserverinfo", Authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
r.HandleFunc("/api/server/usage", Authorize(true, false, "user", http.HandlerFunc(getUsage))).Methods(http.MethodGet)
}
func getUsage(w http.ResponseWriter, r *http.Request) {
type usage struct {
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
Users int `json:"users"`
}
var serverUsage usage
hosts, err := logic.GetAllHosts()
if err == nil {
serverUsage.Hosts = len(hosts)
}
clients, err := logic.GetAllExtClients()
if err == nil {
serverUsage.Clients = len(clients)
}
users, err := logic.GetUsers()
if err == nil {
serverUsage.Users = len(users)
}
networks, err := logic.GetNetworks()
if err == nil {
serverUsage.Networks = len(networks)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(models.SuccessResponse{
Code: http.StatusOK,
Response: serverUsage,
})

}

// swagger:route GET /api/server/status server getStatus
Expand All @@ -41,6 +73,12 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
type status struct {
DB bool `json:"db_connected"`
Broker bool `json:"broker_connected"`
Usage struct {
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
Users int `json:"users"`
} `json:"usage"`
}

currentServerStatus := status{
Expand Down
21 changes: 3 additions & 18 deletions ee/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
// InitEE - Initialize EE Logic
func InitEE() {
setIsEnterprise()
servercfg.Is_EE = true
models.SetLogo(retrieveEELogo())
controller.HttpHandlers = append(
controller.HttpHandlers,
Expand All @@ -27,13 +28,8 @@ func InitEE() {
logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
// == License Handling ==
ValidateLicense()
if Limits.FreeTier {
logger.Log(0, "proceeding with Free Tier license")
logic.SetFreeTierForTelemetry(true)
} else {
logger.Log(0, "proceeding with Paid Tier license")
logic.SetFreeTierForTelemetry(false)
}
logger.Log(0, "proceeding with Paid Tier license")
logic.SetFreeTierForTelemetry(false)
// == End License Handling ==
AddLicenseHooks()
resetFailover()
Expand All @@ -46,17 +42,6 @@ func InitEE() {
logic.AllowClientNodeAccess = eelogic.RemoveDeniedNodeFromClient
}

func setControllerLimits() {
logic.Node_Limit = Limits.Nodes
logic.Users_Limit = Limits.Users
logic.Clients_Limit = Limits.Clients
logic.Free_Tier = Limits.FreeTier
servercfg.Is_EE = true
if logic.Free_Tier {
logic.Networks_Limit = 3
}
}

func resetFailover() {
nets, err := logic.GetNetworks()
if err == nil {
Expand Down
32 changes: 13 additions & 19 deletions ee/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import (
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"time"

"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
"github.com/gravitl/netmaker/netclient/ncutils"
"github.com/gravitl/netmaker/servercfg"
"golang.org/x/crypto/nacl/box"
Expand All @@ -31,8 +32,14 @@ type apiServerConf struct {

// AddLicenseHooks - adds the validation and cache clear hooks
func AddLicenseHooks() {
logic.AddHook(ValidateLicense)
logic.AddHook(ClearLicenseCache)
logic.HookManagerCh <- models.HookDetails{
Hook: ValidateLicense,
Interval: time.Hour,
}
logic.HookManagerCh <- models.HookDetails{
Hook: ClearLicenseCache,
Interval: time.Hour,
}
}

// ValidateLicense - the initial license check for netmaker server
Expand All @@ -58,8 +65,8 @@ func ValidateLicense() error {
}

licenseSecret := LicenseSecret{
UserID: netmakerAccountID,
Limits: getCurrentServerLimit(),
AssociatedID: netmakerAccountID,
Limits: getCurrentServerLimit(),
}

secretData, err := json.Marshal(&licenseSecret)
Expand Down Expand Up @@ -92,17 +99,6 @@ func ValidateLicense() error {
logger.FatalLog0(errValidation.Error())
}

Limits.Networks = math.MaxInt
Limits.FreeTier = license.FreeTier == "yes"
Limits.Clients = license.LimitClients
Limits.Nodes = license.LimitNodes
Limits.Servers = license.LimitServers
Limits.Users = license.LimitUsers
if Limits.FreeTier {
Limits.Networks = 3
}
setControllerLimits()

logger.Log(0, "License validation succeeded!")
return nil
}
Expand Down Expand Up @@ -167,6 +163,7 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro
}

msg := ValidateLicenseRequest{
LicenseKey: servercfg.GetLicenseKey(),
NmServerPubKey: base64encode(publicKeyBytes),
EncryptedPart: base64encode(encryptedData),
}
Expand All @@ -180,9 +177,6 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, erro
if err != nil {
return nil, err
}
reqParams := req.URL.Query()
reqParams.Add("licensevalue", servercfg.GetLicenseKey())
req.URL.RawQuery = reqParams.Encode()
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
client := &http.Client{}
Expand Down
58 changes: 20 additions & 38 deletions ee/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,25 @@ package ee
import "fmt"

const (
api_endpoint = "https://api.controller.netmaker.io/api/v1/license/validate"
api_endpoint = "https://api.accounts.netmaker.io/api/v1/license/validate"
license_cache_key = "license_response_cache"
license_validation_err_msg = "invalid license"
server_id_key = "nm-server-id"
)

var errValidation = fmt.Errorf(license_validation_err_msg)

// Limits - limits to be referenced throughout server
var Limits = GlobalLimits{
Servers: 0,
Users: 0,
Nodes: 0,
Clients: 0,
Networks: 0,
FreeTier: false,
}

// GlobalLimits - struct for holding global limits on this netmaker server in memory
type GlobalLimits struct {
Servers int
Users int
Nodes int
Clients int
FreeTier bool
Networks int
}

// LicenseKey - the license key struct representation with associated data
type LicenseKey struct {
LicenseValue string `json:"license_value"` // actual (public) key and the unique value for the key
Expiration int64 `json:"expiration"`
LimitServers int `json:"limit_servers"`
LimitUsers int `json:"limit_users"`
LimitNodes int `json:"limit_nodes"`
LimitClients int `json:"limit_clients"`
Metadata string `json:"metadata"`
SubscriptionID string `json:"subscription_id"` // for a paid subscription (non-free-tier license)
FreeTier string `json:"free_tier"` // yes if free tier
IsActive string `json:"is_active"` // yes if active
LicenseValue string `json:"license_value"` // actual (public) key and the unique value for the key
Expiration int64 `json:"expiration"`
LimitServers int `json:"limit_servers"`
LimitUsers int `json:"limit_users"`
LimitHosts int `json:"limit_hosts"`
LimitNetworks int `json:"limit_networks"`
LimitClients int `json:"limit_clients"`
Metadata string `json:"metadata"`
IsActive bool `json:"is_active"` // yes if active
}

// ValidatedLicense - the validated license struct
Expand All @@ -53,28 +32,31 @@ type ValidatedLicense struct {

// LicenseSecret - the encrypted struct for sending user-id
type LicenseSecret struct {
UserID string `json:"user_id" binding:"required"` // UUID for user foreign key to User table
Limits LicenseLimits `json:"limits" binding:"required"`
AssociatedID string `json:"associated_id" binding:"required"` // UUID for user foreign key to User table
Limits LicenseLimits `json:"limits" binding:"required"`
}

// LicenseLimits - struct license limits
type LicenseLimits struct {
Servers int `json:"servers" binding:"required"`
Users int `json:"users" binding:"required"`
Nodes int `json:"nodes" binding:"required"`
Clients int `json:"clients" binding:"required"`
Servers int `json:"servers"`
Users int `json:"users"`
Hosts int `json:"hosts"`
Clients int `json:"clients"`
Networks int `json:"networks"`
}

// LicenseLimits.SetDefaults - sets the default values for limits
func (l *LicenseLimits) SetDefaults() {
l.Clients = 0
l.Servers = 1
l.Nodes = 0
l.Hosts = 0
l.Users = 1
l.Networks = 0
}

// ValidateLicenseRequest - used for request to validate license endpoint
type ValidateLicenseRequest struct {
LicenseKey string `json:"license_key" binding:"required"`
NmServerPubKey string `json:"nm_server_pub_key" binding:"required"` // Netmaker server public key used to send data back to Netmaker for the Netmaker server to decrypt (eg output from validating license)
EncryptedPart string `json:"secret" binding:"required"`
}
Expand Down
9 changes: 6 additions & 3 deletions ee/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@ func base64decode(input string) []byte {

return bytes
}

func getCurrentServerLimit() (limits LicenseLimits) {
limits.SetDefaults()
nodes, err := logic.GetAllNodes()
hosts, err := logic.GetAllHosts()
if err == nil {
limits.Nodes = len(nodes)
limits.Hosts = len(hosts)
}
clients, err := logic.GetAllExtClients()
if err == nil {
Expand All @@ -45,5 +44,9 @@ func getCurrentServerLimit() (limits LicenseLimits) {
if err == nil {
limits.Users = len(users)
}
networks, err := logic.GetNetworks()
if err == nil {
limits.Networks = len(networks)
}
return
}
9 changes: 8 additions & 1 deletion logic/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,14 @@ func GetHost(hostid string) (*models.Host, error) {

// CreateHost - creates a host if not exist
func CreateHost(h *models.Host) error {
_, err := GetHost(h.ID.String())
hosts, err := GetAllHosts()
if err != nil && !database.IsEmptyRecord(err) {
return err
}
if len(hosts) >= Hosts_Limit {
return errors.New("free tier limits exceeded on hosts")
}
_, err = GetHost(h.ID.String())
if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
return ErrHostExists
}
Expand Down
13 changes: 11 additions & 2 deletions logic/serverconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import (
"encoding/json"

"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/servercfg"
)

var (
// Node_Limit - dummy var for community
Node_Limit = 1000000000
// Networks_Limit - dummy var for community
Networks_Limit = 1000000000
// Users_Limit - dummy var for community
Users_Limit = 1000000000
// Clients_Limit - dummy var for community
Clients_Limit = 1000000000
// Hosts_Limit - dummy var for community
Hosts_Limit = 1000000000
// Free_Tier - specifies if free tier
Free_Tier = false
)
Expand Down Expand Up @@ -85,3 +86,11 @@ func StoreJWTSecret(privateKey string) error {
}
return database.Insert("nm-jwt-secret", string(data), database.SERVERCONF_TABLE_NAME)
}

func SetFreeTierLimits() {
Free_Tier = true
Users_Limit = servercfg.GetUserLimit()
Clients_Limit = servercfg.GetClientLimit()
Networks_Limit = servercfg.GetNetworkLimit()
Hosts_Limit = servercfg.GetHostLimit()
}
Loading

0 comments on commit 230e062

Please sign in to comment.