Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enterprise edition #1575

Merged
merged 50 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2880dab
feat: added license manager and feature flags
mindhash Sep 5, 2022
f267347
chore: deleted unncessary file
mindhash Sep 5, 2022
a91625c
feat: added feature gating mechanics
mindhash Sep 6, 2022
6bee984
feat: completed org domain api
mindhash Sep 12, 2022
fb246b4
chore: checking in saml auth handler code
mindhash Sep 16, 2022
2d8f7c7
chore: merged develop and resolved conflicts
mindhash Sep 16, 2022
98ed96b
feat: added signup with sso
mindhash Sep 20, 2022
2957f62
fix: solved issue with put domain
mindhash Sep 21, 2022
a9723e2
feat: added login support for admins
mindhash Sep 21, 2022
ac9fd04
fix: resolves issue with license activation
mindhash Sep 22, 2022
a59cf8e
fix: resolved org create issue
mindhash Sep 22, 2022
f8adc58
fix: domain creation issue
mindhash Sep 23, 2022
594c723
chore: removed test reference to localhost
mindhash Sep 26, 2022
3a32fc6
fix: solved an issue with saml auth
mindhash Sep 26, 2022
29904a5
chore: added useful log in license validation routine
mindhash Sep 28, 2022
cc77ae7
chore: added dockerfile for ee
mindhash Sep 28, 2022
710949d
chore: changed dockerfile to dockerfile.ee
mindhash Sep 28, 2022
7046693
feat: added pem support for certificate
mindhash Sep 28, 2022
24d04aa
chore: changed issuer to support active dir setup
mindhash Sep 29, 2022
5ac4a1a
chore: added debug messg to resolve registeruser issue
mindhash Sep 29, 2022
8048889
fix: unescaped source url
mindhash Sep 29, 2022
992056e
fix: removed debug message
mindhash Sep 29, 2022
f9025e7
chore: moved go.mod to top folder
mindhash Sep 29, 2022
7b2d741
chore: added ee folder move
mindhash Sep 29, 2022
f5158d6
Merge branch 'develop' into feat/ee
mindhash Oct 1, 2022
605a89d
chore: fixed some mock setups
mindhash Oct 1, 2022
4f55ce2
Merge branch 'feat/ee' of github.com:mindhash/signoz into feat/ee
mindhash Oct 1, 2022
6989b9d
chore: updated makefile of top folder
mindhash Oct 1, 2022
7a3ad74
chore: fixed querybackend name changes
mindhash Oct 3, 2022
43515d4
chore: added ee/signoz.db to remove it in git
mindhash Oct 3, 2022
b1a39b2
chore: deleted ee/signoz.db
mindhash Oct 3, 2022
7873102
chore: removed .signoz.db
mindhash Oct 3, 2022
0fdea3d
chore: removed wrong db file in repo
mindhash Oct 3, 2022
cdd21ec
chore: added condition to ignore all sqlite db files
mindhash Oct 3, 2022
a1b85ae
chore: 🙈 update gitignore and dockerignore file
prashant-shahi Oct 3, 2022
14e63ac
chore: 🚀 update ee dockerfile
prashant-shahi Oct 3, 2022
416453e
chore: 🚀 include ee build steps in Makefile
prashant-shahi Oct 3, 2022
a3749ff
ci(build-workflow): 👷 include EE query-service
prashant-shahi Oct 3, 2022
407184d
fix: 🐛 resolve issues of build files
prashant-shahi Oct 3, 2022
f5a179b
Merge pull request #1 from prashant-shahi/prashant/ee-build
mindhash Oct 3, 2022
853f110
Merge branch 'develop' into feat/ee
prashant-shahi Oct 3, 2022
97a97b4
fix: 🐛 update package name
prashant-shahi Oct 3, 2022
790f049
fix: move usage reporting to top level ee folder
nityanandagohain Oct 3, 2022
ddf830a
Merge pull request #2 from nityanandagohain/feat/usage-reporting-pr
mindhash Oct 4, 2022
ad5a6d9
fix: resovled some review comments and issue with license page
mindhash Oct 6, 2022
cc37c5c
Merge branch 'feat/ee' of github.com:mindhash/signoz into feat/ee
mindhash Oct 6, 2022
a8d6d9d
chore: restored prom config
mindhash Oct 6, 2022
d822ca1
Merge branch 'develop' into feat/ee
mindhash Oct 6, 2022
9ee3962
chore(ee): 🔧 LD_FLAGS related changes
prashant-shahi Oct 6, 2022
a05a6fd
Merge pull request #3 from prashant-shahi/prashant/ld-flags
mindhash Oct 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: added license manager and feature flags
  • Loading branch information
mindhash committed Sep 5, 2022
commit 2880dab2ae87a547416cfdf250784f5d73a2dd6e
451 changes: 226 additions & 225 deletions pkg/query-service/app/http_handler.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/query-service/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}

telemetry.GetInstance().SetReader(reader)
apiHandler, err := NewAPIHandler(&reader, dao.DB(), rm)
apiHandler, err := NewAPIHandler(reader, dao.DB(), rm)
if err != nil {
return nil, err
}
Expand Down
65 changes: 34 additions & 31 deletions pkg/query-service/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,39 +299,10 @@ func Login(ctx context.Context, request *model.LoginRequest) (*model.LoginRespon
return nil, err
}

accessJwtExpiry := time.Now().Add(JwtExpiry).Unix()

token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": user.Id,
"gid": user.GroupId,
"email": user.Email,
"exp": accessJwtExpiry,
})

accessJwt, err := token.SignedString([]byte(JwtSecret))
if err != nil {
return nil, errors.Errorf("failed to encode jwt: %v", err)
}

refreshJwtExpiry := time.Now().Add(JwtRefresh).Unix()
token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": user.Id,
"gid": user.GroupId,
"email": user.Email,
"exp": refreshJwtExpiry,
})

refreshJwt, err := token.SignedString([]byte(JwtSecret))
if err != nil {
return nil, errors.Errorf("failed to encode jwt: %v", err)
}
userjwt, err := GenerateJWTForUser(&user.User)
mindhash marked this conversation as resolved.
Show resolved Hide resolved

return &model.LoginResponse{
AccessJwt: accessJwt,
AccessJwtExpiry: accessJwtExpiry,
RefreshJwt: refreshJwt,
RefreshJwtExpiry: refreshJwtExpiry,
UserId: user.Id,
userjwt,
}, nil
}

Expand Down Expand Up @@ -375,3 +346,35 @@ func passwordMatch(hash, password string) bool {
}
return true
}

func GenerateJWTForUser(user *model.User) (model.UserJwtObject, error) {
j := model.UserJwtObject{}
var err error
j.AccessJwtExpiry = time.Now().Add(JwtExpiry).Unix()

token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": user.Id,
"gid": user.GroupId,
"email": user.Email,
"exp": j.AccessJwtExpiry,
})

j.AccessJwt, err = token.SignedString([]byte(JwtSecret))
if err != nil {
return j, errors.Errorf("failed to encode jwt: %v", err)
mindhash marked this conversation as resolved.
Show resolved Hide resolved
}

j.RefreshJwtExpiry = time.Now().Add(JwtRefresh).Unix()
token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": user.Id,
"gid": user.GroupId,
"email": user.Email,
"exp": j.RefreshJwtExpiry,
})

j.RefreshJwt, err = token.SignedString([]byte(JwtSecret))
if err != nil {
return j, errors.Errorf("failed to encode jwt: %v", err)
mindhash marked this conversation as resolved.
Show resolved Hide resolved
}
return j, nil
}
5 changes: 5 additions & 0 deletions pkg/query-service/dao/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ func InitDao(engine, path string) error {
return nil
}

// SetDB is used by ee for setting modelDAO
func SetDB(m ModelDao) {
db = m
}

func DB() ModelDao {
if db == nil {
// Should never reach here
Expand Down
5 changes: 5 additions & 0 deletions pkg/query-service/dao/sqlite/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ func InitDB(dataSourceName string) (*ModelDaoSqlite, error) {
return mds, nil
}

// DB returns database connection
func (mds *ModelDaoSqlite) DB() *sqlx.DB {
return mds.db
}

// initializeOrgPreferences initializes in-memory telemetry settings. It is planned to have
// multiple orgs in the system. In case of multiple orgs, there will be separate instance
// of in-memory telemetry for each of the org, having their own settings. As of now, we only
Expand Down
80 changes: 80 additions & 0 deletions pkg/query-service/ee/app/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package api

import (
"github.com/gorilla/mux"
baseapp "go.signoz.io/query-service/app"
"go.signoz.io/query-service/ee/dao"
"go.signoz.io/query-service/ee/interfaces"
"go.signoz.io/query-service/ee/license"
baseint "go.signoz.io/query-service/interfaces"
rules "go.signoz.io/query-service/rules"
"go.signoz.io/query-service/version"
"net/http"
)

type APIHandlerOptions struct {
QueryBackend interfaces.QueryBackend
AppDB dao.ModelDao
RulesManager *rules.Manager
FeatureFlags baseint.FeatureLookup
LicenseManager *license.Manager
}

type APIHandler struct {
opts APIHandlerOptions
baseapp.APIHandler
}

// NewAPIHandler returns an APIHandler
func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
baseHandler, err := baseapp.NewAPIHandler(opts.QueryBackend, opts.AppDB, opts.RulesManager)
if err != nil {
return nil, err
}

ah := &APIHandler{
opts: opts,
APIHandler: *baseHandler,
}
return ah, nil
}

func (ah *APIHandler) FF() baseint.FeatureLookup {
return ah.opts.FeatureFlags
}

func (ah *APIHandler) RM() *rules.Manager {
return ah.opts.RulesManager
}

func (ah *APIHandler) LM() *license.Manager {
return ah.opts.LicenseManager
}

func (ah *APIHandler) AppDB() dao.ModelDao {
return ah.opts.AppDB
}

// RegisterRoutes registers routes for this handler on the given router
func (ah *APIHandler) RegisterRoutes(router *mux.Router) {
// note: add ee override methods first

// routes available only in ee version
router.HandleFunc("/api/v1/apply/license", baseapp.AdminAccess(ah.applyLicense)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/featureFlags", baseapp.OpenAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/loginPrecheck", baseapp.OpenAccess(ah.precheckLogin)).Methods(http.MethodGet)

// paid plans specific routes
router.HandleFunc("/api/v1/organization/{org_id}/complete/saml", baseapp.OpenAccess(ah.ReceiveSAML)).Methods(http.MethodPost)

// base overrides
router.HandleFunc("/api/v1/version", baseapp.OpenAccess(ah.getVersion)).Methods(http.MethodGet)

ah.APIHandler.RegisterRoutes(router)

}

func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
version := version.GetVersion()
ah.WriteJSON(w, r, map[string]string{"version": version, "eeAvailable": "Y"})
}
138 changes: 138 additions & 0 deletions pkg/query-service/ee/app/api/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package api

import (
"context"
"fmt"
"github.com/gorilla/mux"
baseauth "go.signoz.io/query-service/auth"
"go.signoz.io/query-service/ee/constants"
"go.signoz.io/query-service/ee/model"
"go.signoz.io/query-service/ee/saml"
"go.uber.org/zap"
"net/http"
)

// methods that use user authentication
// precheckLogin checks if SSO or SAML is available, the check happens
// when user enters email address in login screen.
func (aH *APIHandler) precheckLogin(w http.ResponseWriter, r *http.Request) {

// todo(amol): validate email with org domain
// email := r.URL.Query().Get("email")

// path := r.URL.Query().Get("path")

// type precheckLoginResponse struct {
// SSOEnabled bool `json:"ssoEnabled"`
// SAMLEnabled bool `json:"samlEnabled"`
// SAMLLoginUrl string `json:"samlLoginUrl"`
// }

// var org *model.Organization
// var apiError *model.ApiError

// if !aH.IsMultiOrgAvailable {
// org, apiError = aH.relationalDB.GetSingleOrg(context.Background())
// if apiError != nil {
// zap.S().Debugf("[precheckLogin] failed to fetch organization: %v", apiError)
// RespondError(w, apiError, nil)
// return
// }
// } else {
// // todo(amol): read email address from request and determine org using domain
// }

// // todo(amol) just responding dummy data for now
// precheckResp := precheckLoginResponse{
// SSOEnabled: org.IsSSOEnabled(),
// SAMLEnabled: org.IsSAMLEnabled(),
// }

// if org.IsSAMLAvailable() {
// loginURL, err := saml.BuildLoginURLWithOrg(org, path)
// if err != nil {
// RespondError(w, &model.ApiError{
// Typ: model.ErrorInternal,
// Err: err,
// }, nil)
// return
// }
// precheckResp.SAMLLoginUrl = loginURL
// }

// aH.WriteJSON(w, r, precheckResp)
}

func (ah *APIHandler) ReceiveSAML(w http.ResponseWriter, r *http.Request) {

domainID := mux.Vars(r)["domain_id"]
redirectUri := constants.GetSAMLRedirectURL()

// get org
domain, apiError := ah.AppDB().GetOrgDomain(context.Background(), domainID)
if apiError != nil {
zap.S().Errorf("[ReceiveSAML] failed to fetch organization (%s): %v", domainID, apiError)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "failed to identify user organization, please contact your administrator"), 301)
return
}

if err := ah.CheckFeature(model.SSO); err != nil {
zap.S().Errorf("[ReceiveSAML] feature unavailable %s in org %s", model.SAML, domainID)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), 301)
return
}

sp, err := saml.PrepRequestWithOrg(domain, "")
if err != nil {
zap.S().Errorf("[ReceiveSAML] failed to prepare saml request for organization (%s): %v", domainID, err)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "failed to send request to SSO, please contact your administrator"), 301)
return
}

err = r.ParseForm()
if err != nil {
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "failed to authenticate with the SSO provider"), 301)
return
}

assertionInfo, err := sp.RetrieveAssertionInfo(r.FormValue("SAMLResponse"))
if err != nil {
zap.S().Errorf("[ReceiveSAML] failed to retrieve assertion info from saml response for organization (%s): %v", domainID, err)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "user not found, please contact your administrator"), 301)
return
}

if assertionInfo.WarningInfo.InvalidTime {
zap.S().Errorf("[ReceiveSAML] expired saml response for organization (%s): %v", domainID, err)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "saml response expired, please contact your administrator"), 301)
return
}

if assertionInfo.WarningInfo.NotInAudience {
zap.S().Errorf("[ReceiveSAML] NotInAudience error for orgID: %s", domainID)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "this app does not have accesss to SSO provider login"), 301)
return
}

email := assertionInfo.NameID
firstName := assertionInfo.Values.Get("FirstName")
lastName := assertionInfo.Values.Get("LastName")

userPayload, err := ah.AppDB().FetchOrRegisterSAMLUser(email, firstName, lastName)
if err != nil {
zap.S().Errorf("[ReceiveSAML] failed to find or register a new user for email %s and org %s", email, domainID)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "failed to authenticate, please contact your administrator"), 301)
return
}

tokenStore, err := baseauth.GenerateJWTForUser(&userPayload.User)
if err != nil {
zap.S().Errorf("[ReceiveSAML] failed to generate access token for email %s and org %s", email, domainID)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "failed to login, please contact your administrator"), 301)
return
}
userID := userPayload.User.Id
nextPage := fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s", redirectUri, tokenStore.AccessJwt, userID, tokenStore.RefreshJwt)

http.Redirect(w, r, nextPage, 301)
}
17 changes: 17 additions & 0 deletions pkg/query-service/ee/app/api/featureFlags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package api

import (
"net/http"
)

// methods that use user session or jwt to return
// user specific data

func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
featureSet := ah.LM().GetFeatureFlags()
ah.WriteJSON(w, r, featureSet)
}

func (ah *APIHandler) CheckFeature(f string) error {
return ah.FF().CheckFeature(f)
}
32 changes: 32 additions & 0 deletions pkg/query-service/ee/app/api/license.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package api

import (
"context"
"encoding/json"
"fmt"
"go.signoz.io/query-service/ee/model"
"net/http"
)

func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
var l model.License

if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
RespondError(w, model.NewBadRequestError(err), nil)
return
}

if l.Key == "" {
RespondError(w, model.NewBadRequestError(fmt.Errorf("license key is required")), nil)
return
}

license, apiError := ah.LM().Activate(ctx, l.Key)
if apiError != nil {
RespondError(w, apiError, nil)
return
}

ah.Respond(w, license)
}
11 changes: 11 additions & 0 deletions pkg/query-service/ee/app/api/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package api

import (
baseapp "go.signoz.io/query-service/app"
"go.signoz.io/query-service/ee/model"
"net/http"
)

func RespondError(w http.ResponseWriter, apiErr *model.ApiError, data interface{}) {
baseapp.RespondError(w, &apiErr.ApiError, data)
}
Loading