package kolide import ( "fmt" "time" "github.com/dgrijalva/jwt-go" "github.com/pkg/errors" "golang.org/x/net/context" ) const ( LicenseGracePeriod = time.Hour * 24 * 60 // 60 days hostLimitUnlimited = 0 ) type LicenseStore interface { // SaveLicense saves jwt formatted customer license information SaveLicense(tokenString, publicKey string) (*License, error) // License returns a structure with the jwt customer license if it exists. License() (*License, error) // LicensePublicKey gets the public key associated with this license LicensePublicKey(tokenString string) (string, error) // RevokeLicense sets revoked status of license RevokeLicense(revoked bool) error } type LicenseService interface { // License returns details of a customer license that determine authorization // to use the Kolide product. If the customer has not uploaded a token, // the license Token will be nil. License(ctx context.Context) (*License, error) // SaveLicense writes jwt token string to database after performing // validation SaveLicense(ctx context.Context, jwtToken string) (*License, error) } // Contains information needed to extract customer license particulars. type License struct { UpdateTimestamp ID uint // Token is a jwt token Token *string `db:"token"` // PublicKey is used to validate the Token and extract claims PublicKey string `db:"key"` // Revoked if true overrides a license that might otherwise be valid Revoked bool // HostCount is the count of enrolled hosts HostCount uint `db:"-"` } // LicenseClaims contains information about the rights of a customer to // use the Kolide product type Claims struct { LicenseUUID string OrganizationName string OrganizationUUID string // HostLimit the maximum number of hosts that a customer can use. 0 is unlimited. HostLimit int // Evaluation indicates that Kolide can be used for eval only. Evaluation bool // ExpiresAt time when license expires ExpiresAt time.Time // HostCount number of enrolled hosts HostCount int } // Expired returns true if the license is expired func (c *Claims) Expired(current time.Time) bool { if c.Evaluation && c.ExpiresAt.Before(current) { return true } if !c.Evaluation && c.ExpiresAt.Add(LicenseGracePeriod).Before(current) { return true } return false } // CanEnrollHost returns true if the user is licensed to enroll additional // hosts func (c *Claims) CanEnrollHost() bool { if c.HostLimit == hostLimitUnlimited { return true } if c.HostCount <= c.HostLimit { return true } return false } // Claims returns information contained in the jwt license token func (l *License) Claims() (*Claims, error) { if l.Token == nil { return nil, errors.New("license missing") } token, err := jwt.Parse(*l.Token, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(l.PublicKey)) if err != nil { return nil, errors.Wrap(err, "reading license token") } return key, nil }) if err != nil { return nil, errors.Wrap(err, "reading licence token") } var result Claims if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { result.LicenseUUID = claims["license_uuid"].(string) result.OrganizationName = claims["organization_name"].(string) result.OrganizationUUID = claims["organization_uuid"].(string) result.HostLimit = int(claims["host_limit"].(float64)) result.Evaluation = claims["evaluation"].(bool) expiry, err := time.Parse(time.RFC3339, claims["expires_at"].(string)) if err != nil { return nil, err } result.ExpiresAt = expiry } else { return nil, errors.New("license token is not valid") } result.HostCount = int(l.HostCount) return &result, nil }