Skip to content

Commit

Permalink
Feat/cloud integrations connection params api key (#6997)
Browse files Browse the repository at this point in the history
* feat: get started on PAT provisioning for AWS integration

* chore: include cloud integration PAT in connection params

* chore: some cleanup

---------

Co-authored-by: Srikanth Chekuri <[email protected]>
  • Loading branch information
raj-k-singh and srikanthccv authored Feb 1, 2025
1 parent c8032f7 commit e33a0fd
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 21 deletions.
160 changes: 140 additions & 20 deletions ee/query-service/app/api/cloudIntegrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import (
"strings"
"time"

"github.com/google/uuid"
"github.com/gorilla/mux"
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
"go.signoz.io/signoz/pkg/query-service/auth"
baseconstants "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/dao"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
Expand All @@ -20,6 +25,7 @@ type CloudIntegrationConnectionParamsResponse struct {
IngestionUrl string `json:"ingestion_url,omitempty"`
IngestionKey string `json:"ingestion_key,omitempty"`
SigNozAPIUrl string `json:"signoz_api_url,omitempty"`
SigNozAPIKey string `json:"signoz_api_key,omitempty"`
}

func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseWriter, r *http.Request) {
Expand All @@ -31,44 +37,64 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
return
}

license, err := ah.LM().GetRepo().GetActiveLicense(r.Context())
currentUser, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, basemodel.InternalError(fmt.Errorf(
"couldn't look for active license: %w", err,
RespondError(w, basemodel.UnauthorizedError(fmt.Errorf(
"couldn't deduce current user: %w", err,
)), nil)
return
}

if license == nil {
RespondError(w, basemodel.ForbiddenError(fmt.Errorf(
"no active license found",
)), nil)
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), currentUser.OrgId, cloudProvider)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't provision PAT for cloud integration:",
), nil)
return
}

ingestionUrl, signozApiUrl, err := getIngestionUrlAndSigNozAPIUrl(r.Context(), license.Key)
if err != nil {
RespondError(w, basemodel.InternalError(fmt.Errorf(
"couldn't deduce ingestion url and signoz api url: %w", err,
)), nil)
result := CloudIntegrationConnectionParamsResponse{
SigNozAPIKey: apiKey,
}

license, apiErr := ah.LM().GetRepo().GetActiveLicense(r.Context())
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't look for active license",
), nil)
return
}

result := CloudIntegrationConnectionParamsResponse{
IngestionUrl: ingestionUrl,
SigNozAPIUrl: signozApiUrl,
if license == nil {
// Return the API Key (PAT) even if the rest of the params can not be deduced.
// Params not returned from here will be requested from the user via form inputs.
// This enables gracefully degraded but working experience even for non-cloud deployments.
zap.L().Info("ingestion params and signoz api url can not be deduced since no license was found")
ah.Respond(w, result)
return
}

ingestionUrl, signozApiUrl, apiErr := getIngestionUrlAndSigNozAPIUrl(r.Context(), license.Key)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't deduce ingestion url and signoz api url",
), nil)
return
}

result.IngestionUrl = ingestionUrl
result.SigNozAPIUrl = signozApiUrl

gatewayUrl := ah.opts.GatewayUrl
if len(gatewayUrl) > 0 {

ingestionKey, err := getOrCreateCloudProviderIngestionKey(
ingestionKey, apiErr := getOrCreateCloudProviderIngestionKey(
r.Context(), gatewayUrl, license.Key, cloudProvider,
)
if err != nil {
RespondError(w, basemodel.InternalError(fmt.Errorf(
"couldn't get or create ingestion key: %w", err,
)), nil)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't get or create ingestion key",
), nil)
return
}

Expand All @@ -81,6 +107,100 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
ah.Respond(w, result)
}

func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId string, cloudProvider string) (
string, *basemodel.ApiError,
) {
integrationPATName := fmt.Sprintf("%s integration", cloudProvider)

integrationUser, apiErr := ah.getOrCreateCloudIntegrationUser(ctx, orgId, cloudProvider)
if apiErr != nil {
return "", apiErr
}

allPats, err := ah.AppDao().ListPATs(ctx)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't list PATs: %w", err.Error(),
))
}
for _, p := range allPats {
if p.UserID == integrationUser.Id && p.Name == integrationPATName {
return p.Token, nil
}
}

zap.L().Info(
"no PAT found for cloud integration, creating a new one",
zap.String("cloudProvider", cloudProvider),
)

newPAT := model.PAT{
Token: generatePATToken(),
UserID: integrationUser.Id,
Name: integrationPATName,
Role: baseconstants.ViewerGroup,
ExpiresAt: 0,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
integrationPAT, err := ah.AppDao().CreatePAT(ctx, newPAT)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't create cloud integration PAT: %w", err.Error(),
))
}
return integrationPAT.Token, nil
}

func (ah *APIHandler) getOrCreateCloudIntegrationUser(
ctx context.Context, orgId string, cloudProvider string,
) (*basemodel.User, *basemodel.ApiError) {
cloudIntegrationUserId := fmt.Sprintf("%s-integration", cloudProvider)

integrationUserResult, apiErr := ah.AppDao().GetUser(ctx, cloudIntegrationUserId)
if apiErr != nil {
return nil, basemodel.WrapApiError(apiErr, "couldn't look for integration user")
}

if integrationUserResult != nil {
return &integrationUserResult.User, nil
}

zap.L().Info(
"cloud integration user not found. Attempting to create the user",
zap.String("cloudProvider", cloudProvider),
)

newUser := &basemodel.User{
Id: cloudIntegrationUserId,
Name: fmt.Sprintf("%s integration", cloudProvider),
Email: fmt.Sprintf("%[email protected]", cloudIntegrationUserId),
CreatedAt: time.Now().Unix(),
OrgId: orgId,
}

viewerGroup, apiErr := dao.DB().GetGroupByName(ctx, baseconstants.ViewerGroup)
if apiErr != nil {
return nil, basemodel.WrapApiError(apiErr, "couldn't get viewer group for creating integration user")
}
newUser.GroupId = viewerGroup.Id

passwordHash, err := auth.PasswordHash(uuid.NewString())
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf(
"couldn't hash random password for cloud integration user: %w", err,
))
}
newUser.Password = passwordHash

integrationUser, apiErr := ah.AppDao().CreateUser(ctx, newUser, false)
if apiErr != nil {
return nil, basemodel.WrapApiError(apiErr, "couldn't create cloud integration user")
}

return integrationUser, nil
}

func getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (
string, string, *basemodel.ApiError,
) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/query-service/app/http_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3939,7 +3939,7 @@ func (aH *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *Au
).Methods(http.MethodPost)

subRouter.HandleFunc(
"/{cloudProvider}/agent-check-in", am.EditAccess(aH.CloudIntegrationsAgentCheckIn),
"/{cloudProvider}/agent-check-in", am.ViewAccess(aH.CloudIntegrationsAgentCheckIn),
).Methods(http.MethodPost)

subRouter.HandleFunc(
Expand Down

0 comments on commit e33a0fd

Please sign in to comment.