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

Option for user to automatically add Amazon Bedrock integrations #1813

Merged
merged 1 commit into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions deepfence_server/apiDocs/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,9 @@ func (d *OpenAPIDocs) AddIntegrationOperations() {
d.AddOperation("addGenerativeAiIntegrationBedrock", http.MethodPost, "/deepfence/generative-ai-integration/bedrock",
"Add AWS Bedrock Generative AI Integration", "Add a new AWS Bedrock Generative AI Integration",
http.StatusOK, []string{tagGenerativeAi}, bearerToken, new(AddGenerativeAiBedrockIntegration), new(MessageResponse))
d.AddOperation("autoAddGenerativeAiIntegrationBedrock", http.MethodPost, "/deepfence/generative-ai-integration/bedrock/auto-add",
"Automatically add AWS Bedrock Generative AI Integration", "Automatically add AWS Bedrock Generative AI Integrations using IAM role",
http.StatusOK, []string{tagGenerativeAi}, bearerToken, new(AutoAddGenerativeAiBedrockIntegration), new(MessageResponse))

d.AddOperation("listGenerativeAiIntegration", http.MethodGet, "/deepfence/generative-ai-integration",
"List Generative AI Integrations", "List all the added Generative AI Integrations",
Expand Down
2 changes: 1 addition & 1 deletion deepfence_server/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/Masterminds/sprig/v3 v3.2.0
github.com/PagerDuty/go-pagerduty v1.7.0
github.com/andygrunwald/go-jira v1.16.0
github.com/aws/aws-sdk-go v1.48.1
github.com/aws/aws-sdk-go v1.48.12
github.com/casbin/casbin/v2 v2.75.0
github.com/deepfence/ThreatMapper/deepfence_utils v0.0.0-00010101000000-000000000000
github.com/docker/docker v24.0.5+incompatible
Expand Down
4 changes: 2 additions & 2 deletions deepfence_server/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ github.com/XSAM/otelsql v0.25.0 h1:ji1G+O45lrmZV9pXv2jQNRzYVFIwEB0jlY0XXdgpuNk=
github.com/XSAM/otelsql v0.25.0/go.mod h1:VfWJ7nRF1t74mSL36s0ksIohT4nmFH5/opajHcmXPFc=
github.com/andygrunwald/go-jira v1.16.0 h1:PU7C7Fkk5L96JvPc6vDVIrd99vdPnYudHu4ju2c2ikQ=
github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU=
github.com/aws/aws-sdk-go v1.48.1 h1:OXPUVL4cLdsDsqkVIuhwY+D389tjI7e1xu0lsDYyeMk=
github.com/aws/aws-sdk-go v1.48.1/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.48.12 h1:n+eGzflzzvYubu2cOjqpVll7lF+Ci0ThyCpg5kzfzbo=
github.com/aws/aws-sdk-go v1.48.12/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/bool64/dev v0.2.29 h1:x+syGyh+0eWtOzQ1ItvLzOGIWyNWnyjXpHIcpF2HvL4=
github.com/bool64/dev v0.2.29/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
Expand Down
162 changes: 120 additions & 42 deletions deepfence_server/handler/generative_ai_integration.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handler

import (
"context"
"database/sql"
"encoding/json"
"errors"
Expand All @@ -10,6 +11,7 @@
api_messages "github.com/deepfence/ThreatMapper/deepfence_server/constants/api-messages"
"github.com/deepfence/ThreatMapper/deepfence_server/model"
generative_ai_integration "github.com/deepfence/ThreatMapper/deepfence_server/pkg/generative-ai-integration"
"github.com/deepfence/ThreatMapper/deepfence_server/pkg/generative-ai-integration/bedrock"
"github.com/deepfence/ThreatMapper/deepfence_utils/directory"
"github.com/deepfence/ThreatMapper/deepfence_utils/encryption"
"github.com/deepfence/ThreatMapper/deepfence_utils/log"
Expand All @@ -18,42 +20,106 @@
httpext "github.com/go-playground/pkg/v5/net/http"
)

var ErrStreamUnsupported = errors.New("streaming unsupported")

func (h *Handler) AddOpenAiIntegration(w http.ResponseWriter, r *http.Request) {
AddGenerativeAiIntegration[model.AddGenerativeAiOpenAIIntegration](w, r, h)
}

func (h *Handler) AddBedrockIntegration(w http.ResponseWriter, r *http.Request) {
AddGenerativeAiIntegration[model.AddGenerativeAiBedrockIntegration](w, r, h)
}
var (
ErrStreamUnsupported = errors.New("streaming unsupported")
ErrGenerativeAIIntegrationExists = BadDecoding{
err: errors.New("similar integration already exists"),
}
ErrBedrockNoActiveModel = BadDecoding{
err: bedrock.ErrBedrockNoActiveModel,
}
)

func AddGenerativeAiIntegration[T model.AddGenerativeAiIntegrationRequest](w http.ResponseWriter, r *http.Request, h *Handler) {
func (h *Handler) AddBedrockIntegrationUsingIAMRole(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var req T
var req model.AutoAddGenerativeAiBedrockIntegration
err := httpext.DecodeJSON(r, httpext.NoQueryParams, MaxPostRequestSize, &req)
if err != nil {
log.Error().Msgf("%v", err)
h.respondError(&BadDecoding{err}, w)
return
}

addModelRequests, err := bedrock.ListBedrockModels(req.AWSRegion)
if err != nil {
log.Error().Msgf("%v", err)
h.respondError(&BadDecoding{err}, w)
return
}

if len(addModelRequests) == 0 {
log.Error().Msgf("%v", ErrBedrockNoActiveModel)
h.respondError(&ErrBedrockNoActiveModel, w)
return
}

ctx := r.Context()
pgClient, err := directory.PostgresClient(ctx)
if err != nil {
h.respondError(&InternalServerError{err}, w)
return
}

obj, err := generative_ai_integration.NewGenerativeAiIntegration(ctx, req)
// encrypt secret
aesValue, err := model.GetAESValueForEncryption(ctx, pgClient)
if err != nil {
log.Error().Msgf(err.Error())
h.respondError(&InternalServerError{err}, w)
return
}
aes := encryption.AES{}
err = json.Unmarshal(aesValue, &aes)
if err != nil {
log.Error().Msgf(err.Error())
h.respondError(&InternalServerError{err}, w)
return
}
user, statusCode, _, err := h.GetUserFromJWT(ctx)
if err != nil {
h.respondWithErrorCode(err, w, statusCode)
return
}

for _, addModelRequest := range addModelRequests {
err = h.AddGenerativeAiIntegrationHelper(ctx, addModelRequest, aes, user, pgClient)
if err != nil {
log.Warn().Msgf(err.Error())
continue
}
}

httpext.JSON(w, http.StatusOK, model.MessageResponse{Message: api_messages.SuccessIntegrationCreated})

Check failure on line 91 in deepfence_server/handler/generative_ai_integration.go

View workflow job for this annotation

GitHub Actions / lint-server

Error return value of `httpext.JSON` is not checked (errcheck)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we checking the return value of this function ?

}

func (h *Handler) AddOpenAiIntegration(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var req model.AddGenerativeAiOpenAIIntegration
err := httpext.DecodeJSON(r, httpext.NoQueryParams, MaxPostRequestSize, &req)
if err != nil {
log.Error().Msgf("%v", err)
h.respondError(&BadDecoding{err}, w)
return
}
err = obj.ValidateConfig(h.Validator)
h.AddGenerativeAiIntegration(req, w, r)
}

func (h *Handler) AddBedrockIntegration(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var req model.AddGenerativeAiBedrockIntegration
err := httpext.DecodeJSON(r, httpext.NoQueryParams, MaxPostRequestSize, &req)
if err != nil {
h.respondError(&ValidatorError{err: err}, w)
log.Error().Msgf("%v", err)
h.respondError(&BadDecoding{err}, w)
return
}
h.AddGenerativeAiIntegration(req, w, r)
}

func (h *Handler) AddGenerativeAiIntegration(req model.AddGenerativeAiIntegrationRequest, w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pgClient, err := directory.PostgresClient(ctx)
if err != nil {
h.respondError(&InternalServerError{err}, w)
return
}

Expand All @@ -71,40 +137,60 @@
h.respondError(&InternalServerError{err}, w)
return
}
err = obj.EncryptSecret(aes)
user, statusCode, _, err := h.GetUserFromJWT(ctx)
if err != nil {
log.Error().Msgf(err.Error())
h.respondError(&InternalServerError{err}, w)
h.respondWithErrorCode(err, w, statusCode)
return
}

// add integration to database
// before that check if integration already exists
integrationExists, err := req.IntegrationExists(ctx, pgClient)
err = h.AddGenerativeAiIntegrationHelper(ctx, req, aes, user, pgClient)
if err != nil {
log.Error().Msgf(err.Error())
h.respondError(&InternalServerError{err}, w)
h.respondError(err, w)
return
}
if integrationExists {
err = httpext.JSON(w, http.StatusBadRequest, model.ErrorResponse{Message: api_messages.ErrIntegrationExists})
if err != nil {
log.Error().Msg(err.Error())
}
return

h.AuditUserActivity(r, EventGenerativeAIIntegration, ActionCreate, map[string]interface{}{"integration_type": req.GetIntegrationType()}, true)

err = httpext.JSON(w, http.StatusOK, model.MessageResponse{Message: api_messages.SuccessIntegrationCreated})
if err != nil {
log.Error().Msg(err.Error())
}
}

user, statusCode, _, err := h.GetUserFromJWT(ctx)
func (h *Handler) AddGenerativeAiIntegrationHelper(ctx context.Context, req model.AddGenerativeAiIntegrationRequest, aes encryption.AES, user *model.User, pgClient *postgresqlDb.Queries) error {
obj, err := generative_ai_integration.NewGenerativeAiIntegration(ctx, req)
if err != nil {
h.respondWithErrorCode(err, w, statusCode)
return
return &BadDecoding{err}
}
err = obj.ValidateConfig(h.Validator)
if err != nil {
return &ValidatorError{err: err}
}
err = obj.VerifyAuth(ctx)
if err != nil {
return &BadDecoding{err: err}
}

err = obj.EncryptSecret(aes)
if err != nil {
return err
}

// add integration to database
// before that check if integration already exists
integrationExists, err := req.IntegrationExists(ctx, pgClient)
if err != nil {
return err
}
if integrationExists {
return &ErrGenerativeAIIntegrationExists
}

// store the integration in db
bConfig, err := json.Marshal(obj)
if err != nil {
h.respondWithErrorCode(err, w, statusCode)
return
return err
}

arg := postgresqlDb.CreateGenerativeAiIntegrationParams{
Expand All @@ -115,22 +201,14 @@
}
dbIntegration, err := pgClient.CreateGenerativeAiIntegration(ctx, arg)
if err != nil {
log.Error().Msgf(err.Error())
h.respondError(&InternalServerError{err}, w)
return
return err
}

h.AuditUserActivity(r, EventGenerativeAIIntegration, ActionCreate, map[string]interface{}{"integration_type": req.GetIntegrationType()}, true)

err = pgClient.UpdateGenerativeAiIntegrationDefault(ctx, dbIntegration.ID)
if err != nil {
log.Warn().Msgf(err.Error())
}

err = httpext.JSON(w, http.StatusOK, model.MessageResponse{Message: api_messages.SuccessIntegrationCreated})
if err != nil {
log.Error().Msg(err.Error())
}
return nil
}

func (h *Handler) GetGenerativeAiIntegrations(w http.ResponseWriter, r *http.Request) {
Expand Down
4 changes: 4 additions & 0 deletions deepfence_server/model/generative_ai_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,7 @@ type GenerativeAiIntegrationListResponse struct {
LastErrorMsg string `json:"last_error_msg"`
DefaultIntegration bool `json:"default_integration"`
}

type AutoAddGenerativeAiBedrockIntegration struct {
AWSRegion string `json:"aws_region" validate:"required,oneof=us-east-1 us-east-2 us-west-1 us-west-2 af-south-1 ap-east-1 ap-south-1 ap-northeast-1 ap-northeast-2 ap-northeast-3 ap-southeast-1 ap-southeast-2 ap-southeast-3 ca-central-1 eu-central-1 eu-west-1 eu-west-2 eu-west-3 eu-south-1 eu-north-1 me-south-1 me-central-1 sa-east-1 us-gov-east-1 us-gov-west-1" required:"true" enum:"us-east-1,us-east-2,us-west-1,us-west-2,af-south-1,ap-east-1,ap-south-1,ap-northeast-1,ap-northeast-2,ap-northeast-3,ap-southeast-1,ap-southeast-2,ap-southeast-3,ca-central-1,eu-central-1,eu-west-1,eu-west-2,eu-west-3,eu-south-1,eu-north-1,me-south-1,me-central-1,sa-east-1,us-gov-east-1,us-gov-west-1"`
}
38 changes: 38 additions & 0 deletions deepfence_server/pkg/generative-ai-integration/bedrock/aimodels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package bedrock

import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/bedrock"
"github.com/deepfence/ThreatMapper/deepfence_server/model"
)

// ListBedrockModels Fetch enabled Bedrock models using IAM roles
func ListBedrockModels(region string) ([]model.AddGenerativeAiIntegrationRequest, error) {
sess, err := session.NewSession(&aws.Config{
Region: aws.String(region),
})
if err != nil {
return nil, err
}
svc := bedrock.New(sess)
models, err := svc.ListFoundationModels(&bedrock.ListFoundationModelsInput{
ByOutputModality: &textModality,
})
if err != nil {
return nil, err
}
resp := []model.AddGenerativeAiIntegrationRequest{}
for _, modelSummary := range models.ModelSummaries {
if *modelSummary.ModelLifecycle.Status == modelLifecycleActive {
if _, ok := BedrockModelBody[*modelSummary.ModelId]; ok {
resp = append(resp, model.AddGenerativeAiBedrockIntegration{
AWSRegion: region,
UseIAMRole: true,
ModelID: *modelSummary.ModelId,
})
}
}
}
return resp, nil
}
18 changes: 18 additions & 0 deletions deepfence_server/pkg/generative-ai-integration/bedrock/bedrock.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ func (b *Bedrock) ValidateConfig(validate *validator.Validate) error {
return validate.Struct(b)
}

func (b *Bedrock) VerifyAuth(ctx context.Context) error {
var err error
if b.UseIAMRole {
_, err = session.NewSession(&aws.Config{
Region: aws.String(b.AWSRegion),
})
} else {
_, err = session.NewSession(&aws.Config{
Region: aws.String(b.AWSRegion),
Credentials: credentials.NewStaticCredentials(b.AWSAccessKey, b.AWSSecretKey, ""),
})
}
if err != nil {
return err
}
return nil
}

func (b *Bedrock) EncryptSecret(aes encryption.AES) error {
var err error
b.AWSSecretKey, err = aes.Encrypt(b.AWSSecretKey)
Expand Down
17 changes: 17 additions & 0 deletions deepfence_server/pkg/generative-ai-integration/bedrock/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package bedrock

import (
"fmt"
"strings"

"github.com/deepfence/ThreatMapper/deepfence_server/pkg/generative-ai-integration/common"
)

Expand All @@ -16,13 +19,27 @@
MetaLlama2Chat13BV1 = "meta.llama2-13b-chat-v1"
CohereCommandTextV14 = "cohere.command-text-v14"
CohereCommandLightTextV14 = "cohere.command-light-text-v14"

modelLifecycleActive = "ACTIVE"
)

var (
textModality = "TEXT"

contentTypeHeader = "application/json"
acceptHeader = "*/*"

ErrBedrockNoActiveModel error
)

func init() {
supportedModels := ""
for modelID, _ := range BedrockModelBody {

Check failure on line 37 in deepfence_server/pkg/generative-ai-integration/bedrock/types.go

View workflow job for this annotation

GitHub Actions / lint-server

File is not `gofmt`-ed with `-s` (gofmt)
supportedModels += modelID + ", "
}
ErrBedrockNoActiveModel = fmt.Errorf("no active model, one of these should be active: %s", strings.Trim(supportedModels, ", "))
}

type Bedrock struct {
common.GenerativeAiIntegrationCommon
AWSAccessKey string `json:"aws_access_key" validate:"omitempty,min=16,max=128"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func NewGenerativeAiIntegrationFromDBEntry(ctx context.Context, integrationType
// GenerativeAiIntegration is the interface for all integrations
type GenerativeAiIntegration interface {
ValidateConfig(*validator.Validate) error
VerifyAuth(ctx context.Context) error
GenerateCloudPostureQuery(model.GenerativeAiIntegrationRequest) (string, error)
GenerateLinuxPostureQuery(model.GenerativeAiIntegrationRequest) (string, error)
GenerateKubernetesPostureQuery(model.GenerativeAiIntegrationRequest) (string, error)
Expand Down
Loading
Loading