diff --git a/src/authnull-db-agent b/authnull-db-agent old mode 100755 new mode 100644 similarity index 67% rename from src/authnull-db-agent rename to authnull-db-agent index 27664f9..ee1842f Binary files a/src/authnull-db-agent and b/authnull-db-agent differ diff --git a/go.mod b/go.mod index a540ed1..0bbb475 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,11 @@ module github.com/authnull0/database-agent go 1.22.1 -require github.com/spf13/viper v1.19.0 +require ( + github.com/google/uuid v1.4.0 + github.com/joho/godotenv v1.5.1 + github.com/spf13/viper v1.19.0 +) require ( filippo.io/edwards25519 v1.1.0 // indirect diff --git a/go.sum b/go.sum index dd57622..da978ee 100644 --- a/go.sum +++ b/go.sum @@ -5,9 +5,13 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJc github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= @@ -16,10 +20,20 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -31,6 +45,10 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -56,6 +74,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= @@ -88,6 +107,8 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/src/pkg/checkout.go b/src/pkg/checkout.go new file mode 100644 index 0000000..4d635a5 --- /dev/null +++ b/src/pkg/checkout.go @@ -0,0 +1,610 @@ +package pkg + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "database/sql" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "log" + + "math/rand" + "net/http" + "strconv" + "time" + + "github.com/authnull0/database-agent/utils" + "github.com/google/uuid" +) + +type CreateDatabaseCredentialResponseDto struct { + Status string `json:"status"` + Message string `json:"message"` + Code int `json:"code"` + CredentialId int `json:"credentialId"` +} + +type GetAllJobQueueRequest struct { + OrgID int `json:"org_id"` + TenantID int `json:"tenant_id"` + Host string `json:"host"` + DbName string `json:"db_name"` +} +type GetAllJobQueueResponse struct { + Code string `json:"code"` + Status string `json:"status"` + Message string `json:"message"` + DbUserName string `json:"db_user_name"` + Data []JobQueue `json:"data"` +} +type JobQueue struct { + PolicyID uuid.UUID `gorm:"primaryKey;column:id"` + ID int `gorm:"primaryKey;column:id"` + JobName string `gorm:"column:job_name"` + Status string `gorm:"column:status"` + DbUserID int `gorm:"column:db_user_id"` + DbID int `gorm:"column:db_id"` + WalletUserID int `gorm:"column:wallet_user_id"` + Host string `gorm:"column:host"` + DomainID int `gorm:"column:domain_id;default:0"` + IssuerID int `gorm:"column:issuer_id;default:0"` + Port *int `gorm:"column:port"` + CredentialID *int `gorm:"column:credential_id"` + Table_Name string `gorm:"column:table_name"` + Fields string `gorm:"column:fields"` + Privileges string `gorm:"column:privileges"` + UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP"` + CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP"` +} +type CreateDatabaseCredentialRequestDto struct { + OrgId int `json:"orgId"` + TenantId int `json:"tenantId"` + WalletUserId int `json:"userId"` + IssuerId int `json:"issuerId"` + Host string `json:"host"` + CredentialType string `json:"credentialType"` + DatabaseName string `json:"database_name"` + Tables []string `json:"tables"` + FieldMasking map[string][]string `json:"field_masking"` + DBUser string `json:"user"` + Privilege []string `json:"privilege"` + Password string `json:"password"` +} + +type GetPolicyDetails struct { + OrgId int `json:"orgId"` + TenantId int `json:"tenantId"` + PolicyID uuid.UUID `json:"policyId"` +} +type GetPolicyDetailsResponse struct { + Code int `json:"code"` + Status string `json:"status"` + Message string `json:"message"` + Data PolicyJSON `json:"data"` +} + +type PolicyJSON struct { + PolicyName string `json:"policyName" binding:"required"` + PolicyType string `json:"policyType" binding:"required"` + Endpoints Endpoints `json:"endpoints,omitempty"` + Domain Domain `json:"domain,omitempty"` + AD AD `json:"ad,omitempty"` + ADGroupInfra AdGroupPolicy `json:"infra,omitempty"` + Networks RadiusNetwork `json:"networks,omitempty"` + Dit Dit `json:"dit,omitempty"` + ServiceAccount ServiceAccount `json:"serviceaccount,omitempty"` + Database Database `json:"database,omitempty"` + Permissions Permission `json:"permissions"` +} + +type Database struct { + DatabaseName string `json:"database_name"` + Tables []string `json:"tables"` + FieldMasking map[string][]string `json:"field_masking"` + User string `json:"user"` + Privilege []string `json:"privilege"` +} + +type Endpoints struct{} // Placeholder for missing struct +type Domain struct{} // Placeholder for missing struct +type AD struct{} // Placeholder for missing struct +type AdGroupPolicy struct{} // Placeholder for missing struct +type RadiusNetwork struct{} // Placeholder for missing struct +type Dit struct{} // Placeholder for missing struct +type ServiceAccount struct{} // Placeholder for missing struct +type Permission struct{} // Placeholder for missing struct + +func PollCheckoutJob(db *sql.DB, dbName string, Config DBConfig) error { + + //API call to get all jobs from the queue + url := "https://prod.api.authnull.com/api/v1/databaseService/getJobQueue" + orgID, _ := strconv.Atoi(Config.OrgID) + tenantID, _ := strconv.Atoi(Config.TenantID) + + ipAddr, err := utils.GetPublicIP() + if err != nil { + fmt.Println("Failed to get PublicIp Address", err) + //return "" + } + log.Default().Println("IP Address:", ipAddr) + payload := GetAllJobQueueRequest{ + OrgID: orgID, + TenantID: tenantID, + Host: ipAddr, + DbName: dbName, + } + + payloadBytes, err := json.Marshal(payload) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes)) + + if err != nil { + log.Printf("Error while creating request: %v", err) + return err + } + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + //Unmarshal the response + var response GetAllJobQueueResponse + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + log.Printf("Error while reading response body: %v", err) + return err + } + err = json.Unmarshal(bodyBytes, &response) + if err != nil { + log.Printf("Error while unmarshalling response: %v", err) + } + if response.Code != "200" { + log.Printf("Error in response: %s", response.Message) + return fmt.Errorf("error in response: %s", response.Message) + } + log.Printf("Response: %s", response.Message) + if len(response.Data) == 0 { + log.Printf("No jobs found in the queue") + return nil + } + //Iterate through the jobs and process them + for _, job := range response.Data { + //Call Other Function to rotate the Password for the DB User in the Database + policyDetails, err := FetchPolicyDetails(orgID, tenantID, job.PolicyID) + if err != nil { + log.Printf("Error fetching policy details for job %s: %v", job.JobName, err) + continue + } + log.Printf("Policy details retrieved - Tables: %v, FieldMasking: %v, Privileges: %v", + policyDetails.Data.Database.Tables, policyDetails.Data.Database.FieldMasking, policyDetails.Data.Database.Privilege) + + fmt.Println("Policy details:", policyDetails) + + fmt.Println("Job Details :", job) + + success, err := GenerateCredentials(db, Config, dbName, response.DbUserName, job.Host, + job.WalletUserID, job.IssuerID, job.Table_Name, job.Fields, job.Privileges, job.DbUserID, + job.PolicyID, policyDetails) + if err != nil { + log.Printf("Error while generating credentials: %v", err) + continue + } + if success { + log.Printf("Credentials generated successfully for job: %s", job.JobName) + //Call Update Job API to update the job status to completed + updateJobURL := "https://prod.api.authnull.com/api/v1/databaseService/updateQueue" + updateJobPayload := map[string]interface{}{ + "org_id": orgID, + "tenant_id": tenantID, + "job_id": job.ID, + } + updateJobPayloadBytes, err := json.Marshal(updateJobPayload) + if err != nil { + log.Printf("Error while marshalling update job payload: %v", err) + continue + } + updateJobReq, err := http.NewRequest("POST", updateJobURL, bytes.NewBuffer(updateJobPayloadBytes)) + if err != nil { + log.Printf("Error while creating update job request: %v", err) + continue + } + updateJobReq.Header.Set("Content-Type", "application/json") + updateJobClient := &http.Client{} + updateJobResp, err := updateJobClient.Do(updateJobReq) + if err != nil { + return err + } + + defer updateJobResp.Body.Close() + if updateJobResp.StatusCode != http.StatusOK { + log.Printf("Error while updating job status: %s", updateJobResp.Status) + } + log.Printf("Job status updated successfully for job: %s", job.JobName) + + } else { + log.Printf("Error while generating credentials for job: %s", job.JobName) + } + + } + + return nil + +} + +func FetchPolicyDetails(orgID int, tenantID int, policyID uuid.UUID) (*GetPolicyDetailsResponse, error) { + url := "https://prod.api.authnull.com/api/v1/policyService/getPolicyDetails" + + payload := GetPolicyDetails{ + OrgId: orgID, + TenantId: tenantID, + PolicyID: policyID, + } + + payloadBytes, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("failed to marshal policy request: %w", err) + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes)) + if err != nil { + return nil, fmt.Errorf("failed to create HTTP request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to execute HTTP request: %w", err) + } + defer resp.Body.Close() + + // Check HTTP status code + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("API returned non-OK status: %d, body: %s", resp.StatusCode, string(bodyBytes)) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + // Parse the response + var apiResponse GetPolicyDetailsResponse + if err := json.Unmarshal(body, &apiResponse); err != nil { + return nil, fmt.Errorf("failed to parse policy response: %w", err) + } + + if apiResponse.Code != 200 { + return nil, fmt.Errorf("invalid policy response: %s (code: %d)", apiResponse.Message, apiResponse.Code) + } + + if apiResponse.Data.Database.Tables == nil { + return nil, errors.New("policy response contains no tables data") + } + + return &apiResponse, nil +} + +// New function to encrypt a string using AES +func EncryptAES(plaintext string, key []byte) (string, error) { + // Create a new AES cipher block + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + // Create a byte array with the plaintext + plaintextBytes := []byte(plaintext) + + // The IV needs to be unique, but not secure + ciphertext := make([]byte, aes.BlockSize+len(plaintextBytes)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(cryptoRand.Reader, iv); err != nil { + return "", err + } + + // Use CFB mode for encryption + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintextBytes) + + // Return the encrypted bytes as a hex string + return hex.EncodeToString(ciphertext), nil +} + +func GenerateCredentials(db *sql.DB, Config DBConfig, dbName string, dbUserName string, host string, + WalletUserID int, IssuerId int, TableName string, Fields string, Privlege string, DbUserID int, + policyID uuid.UUID, policyDetails *GetPolicyDetailsResponse) (bool, error) { + + // Validate policy details + if policyDetails == nil { + return false, errors.New("policy details cannot be nil") + } + + if len(policyDetails.Data.Database.Tables) == 0 { + return false, errors.New("no tables found in policy details") + } + + if len(policyDetails.Data.Database.Privilege) == 0 { + return false, errors.New("no privileges found in policy details") + } + //Rotate the Credentials for the DB User in the Database + //Step1 : Generate a Random Password for the DB User + //password, err := GenerateRandomPassword(16) + //if err != nil { + // log.Printf("Error while generating random password: %v", err) + // return false, err + // } + var password string + proxySQLDB, err := ConnectToProxysqlDB(Config) + if err != nil { + log.Printf("Error while connecting to ProxySQL database: %v", err) + return false, err + } + // Before checking the password, first verify the user exists + var userExists int + checkUserExistsQuery := fmt.Sprintf("SELECT COUNT(*) FROM mysql_users WHERE username = '%s'", dbUserName) + err = proxySQLDB.QueryRow(checkUserExistsQuery).Scan(&userExists) + if err != nil { + log.Printf("Error checking if user exists in ProxySQL: %v", err) + return false, err + } + + if userExists > 0 { + // User exists, let's get the password + var existingPassword string + checkExistingPasswordQuery := fmt.Sprintf("SELECT password FROM mysql_users WHERE username = '%s'", dbUserName) + err = proxySQLDB.QueryRow(checkExistingPasswordQuery).Scan(&existingPassword) + if err != nil { + log.Printf("Error retrieving password for user %s: %v", dbUserName, err) + return false, err + } + + if existingPassword != "" { + log.Printf("Existing password found for user %s, skipping password rotation", dbUserName) + password = existingPassword + } else { + log.Printf("User exists but has empty password, generating new one") + password, err = GenerateRandomPassword(16) + if err != nil { + return false, err + } + } + } else { + // User doesn't exist, generate new password + password, err = GenerateRandomPassword(16) + if err != nil { + return false, err + } + } + var dbhost string + err = db.QueryRow("SELECT host FROM mysql.user WHERE user = ? LIMIT 1", dbUserName).Scan(&dbhost) + if err != nil { + log.Printf("Error fetching host for user %s: %v", dbUserName, err) + return false, err + } + // Check if the user exists with the correct host + checkUserQuery1 := fmt.Sprintf("SELECT COUNT(*) FROM mysql.user WHERE user = '%s' AND host = '%s'", dbUserName, dbhost) + alterPasswdQuery := fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED BY '%s'", dbUserName, dbhost, password) + updatePasswordQuery := fmt.Sprintf("UPDATE mysql_users SET password = '%s' WHERE username = '%s'", password, dbUserName) + var userCount1 int + err = db.QueryRow(checkUserQuery1).Scan(&userCount1) + if err != nil { + log.Printf("Error checking user: %v", err) + return false, err + } + + if userCount1 == 0 { + // Create user with the correct host + createUserQuery := fmt.Sprintf("CREATE USER '%s'@'%s' IDENTIFIED BY '%s'", dbUserName, dbhost, password) + _, err = db.Exec(createUserQuery) + if err != nil { + log.Printf("Error creating user: %v", err) + return false, err + } + } else { + // Update password for the correct host + _, err = db.Exec(alterPasswdQuery) + if err != nil { + log.Printf("Error updating password: %v", err) + return false, err + } + log.Printf("Password updated successfully for user %s@%s", dbUserName, dbhost) + } + + //COnnect to ProxysqlDB + //proxySQLDB, err := ConnectToProxysqlDB(Config) + //if err != nil { + // log.Printf("Error while connecting to ProxySQL database: %v", err) + //} + //Create the user in ProxySQL + // Check if the user already exists in ProxySQL + checkUserQuery := fmt.Sprintf("SELECT COUNT(*) FROM mysql_users WHERE username = '%s'", dbUserName) + var userCount2 int + err = proxySQLDB.QueryRow(checkUserQuery).Scan(&userCount2) + if err != nil { + log.Printf("Error while checking existence of user %s in ProxySQL: %v", dbUserName, err) + return false, err + } + + if userCount2 == 0 { + // Create the user if it does not exist + createUserQuery := fmt.Sprintf("INSERT INTO mysql_users (username, password, active, use_ssl) VALUES ('%s', '%s', 1, 0)", dbUserName, password) + _, err = proxySQLDB.Exec(createUserQuery) + + if err != nil { + log.Printf("Error while creating user %s in ProxySQL: %v", dbUserName, err) + return false, err + } + log.Printf("User %s created successfully in ProxySQL", dbUserName) + } else { + log.Printf("User %s already exists in ProxySQL", dbUserName) + } + + // Update the password for the user in ProxySQL + _, err = proxySQLDB.Exec(updatePasswordQuery) + if err != nil { + log.Printf("Error while updating password for user %s in ProxySQL: %v", dbUserName, err) + return false, err + } + log.Printf("Password for user %s updated successfully in ProxySQL", dbUserName) + + _, err = proxySQLDB.Exec("LOAD MYSQL USERS TO RUNTIME;") + if err != nil { + log.Printf("Error loading users to runtime in ProxySQL: %v", err) + return false, err + } + + _, err = proxySQLDB.Exec("SAVE MYSQL USERS TO DISK;") + if err != nil { + log.Printf("Error saving users to disk in ProxySQL: %v", err) + return false, err + } + + orgId, _ := strconv.Atoi(Config.OrgID) + tenantId, _ := strconv.Atoi(Config.TenantID) + + tables := policyDetails.Data.Database.Tables + fieldMasking := policyDetails.Data.Database.FieldMasking + privilege := policyDetails.Data.Database.Privilege + + // Step 3: Encrypt the password before sending it to the API + // You need to define this AES key somewhere secure in your application + encryptionKey := []byte("84sF#v7Fpt!L#PesYb^AezXrUn2kE%5v") // This should be 16, 24, or 32 bytes for AES-128, AES-192, or AES-256 + + encryptedPassword, err := EncryptAES(password, encryptionKey) + if err != nil { + log.Printf("Error encrypting password: %v", err) + return false, err + } + + //Create the request body with encrypted password + databaseCredentialRequest := CreateDatabaseCredentialRequestDto{ + OrgId: orgId, + TenantId: tenantId, + WalletUserId: WalletUserID, + IssuerId: IssuerId, + Host: host, + CredentialType: "DATABASE", + DatabaseName: dbName, + Password: encryptedPassword, + Tables: tables, + FieldMasking: fieldMasking, + DBUser: dbUserName, + Privilege: privilege, + } + + //Call the API + credentialID, err := CallCreateDatabaseCredentialAPI(databaseCredentialRequest) + if err != nil { + log.Printf("Error while calling Create Database Credential API: %v", err) + return false, err + } + log.Default().Println("The cred id is:", credentialID) + //call policy credential mapping + err = CallPolicyCredentialMapping(orgId, policyID, tenantId, credentialID) + if err != nil { + log.Printf("Error while calling Update Policy Credential Mapping API: %v", err) + return false, err + } + //Step 4 : Return True if the password is updated successfully + return true, nil +} + +func GenerateRandomPassword(length int) (string, error) { + // Generate a random password of the given length + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+[]{}|;:,.<>?" + b := make([]byte, length) + seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) // Seed the random number generator + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] // Select a random character from the charset + } + return string(b), nil +} +func CallCreateDatabaseCredentialAPI(databaseCredentialRequest CreateDatabaseCredentialRequestDto) (int, error) { + log.Default().Println("Entered CallCreateDatabaseCredentialAPI") + client := &http.Client{} + //Marshal the request body + databaseCredentialRequestBytes, err := json.Marshal(databaseCredentialRequest) + if err != nil { + return 0, errors.New("failed to marshal request body: " + err.Error()) + } + log.Default().Println("Successfully Marshalled Request Body") + url := "https://prod.api.authnull.com/api/v1/credential/createDatabaseCredential" + log.Default().Println("URL", url) + //Create the request + req, err := http.NewRequest("POST", url, bytes.NewBuffer(databaseCredentialRequestBytes)) + if err != nil { + return 0, errors.New("failed to create request: " + err.Error()) + } + log.Default().Println("Successfully Created Request") + req.Header.Set("Content-Type", "application/json") + log.Default().Println("Successfully Set Header", req) + //Execute the request + resp, err := client.Do(req) + if err != nil { + return 0, errors.New("failed to execute request: " + err.Error()) + } + defer resp.Body.Close() + log.Default().Println("response Status:", resp.Status) + log.Default().Println("response :", resp) + var response CreateDatabaseCredentialResponseDto + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return 0, errors.New("failed to decode response: " + err.Error()) + } + + log.Default().Println("Successfully received credential ID:", response.CredentialId) + + return response.CredentialId, nil +} + +//func call to call policy credential mapping from policy-service +//payload will be the orgid,tenantid,policyid and credential id + +func CallPolicyCredentialMapping(orgId int, policyId uuid.UUID, tenantId int, credentialId int) error { + payload := struct { + OrgId int `json:"org_id"` + TenantId int `json:"tenant_id"` + PolicyId uuid.UUID `json:"policy_id"` + CredentialId int `json:"credential_id"` + }{ + OrgId: orgId, + TenantId: tenantId, + PolicyId: policyId, + CredentialId: credentialId, + } + + jsonData, err := json.Marshal(payload) + if err != nil { + return errors.New("failed to marshal: " + err.Error()) + } + log.Default().Println(string(jsonData)) + client := &http.Client{} + url := "https://prod.api.authnull.com/api/v1/policyService/updatePolicyCredentialMapping" + log.Default().Println("URL", url) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return errors.New("failed to create request: " + err.Error()) + } + log.Default().Println("Successfully Created Request") + req.Header.Set("Content-Type", "application/json") + log.Default().Println("Successfully Set Header", req) + resp, err := client.Do(req) + if err != nil { + return errors.New("failed to execute request: " + err.Error()) + } + defer resp.Body.Close() + log.Default().Println("response Status:", resp.Status) + log.Default().Println("response :", resp) + return nil +} diff --git a/src/pkg/objects.go b/src/pkg/objects.go index 368dc7a..d3312df 100644 --- a/src/pkg/objects.go +++ b/src/pkg/objects.go @@ -22,3 +22,4 @@ type InstanceCreatedResponse struct { // message Message string } + diff --git a/src/pkg/synchronization.go b/src/pkg/synchronization.go index 936dfec..27ca2c3 100644 --- a/src/pkg/synchronization.go +++ b/src/pkg/synchronization.go @@ -20,6 +20,17 @@ func ConnectToDB(config DBConfig) (*sql.DB, error) { return db, nil } +func ConnectToProxysqlDB(config DBConfig) (*sql.DB, error) { + var dsn string + dsn = fmt.Sprintf("admin,test:%s@tcp(%s:%s)/", "admin", "127.0.0.1", "6032") + + db, err := sql.Open(config.DBType, dsn) + if err != nil { + return nil, err + } + return db, nil +} + // checks if a given database is a system default database func isSystemDatabase(dbName, dbType string) bool { systemDatabases := map[string][]string{ @@ -110,6 +121,11 @@ func FetchDatabaseDetails(db *sql.DB, config DBConfig) error { } log.Println("FetchTables Ended") + err = PollCheckoutJob(db, dbName, config) + if err != nil { + log.Printf("Failed to poll checkout job: %v", err) + } + } return nil