Skip to content

Commit

Permalink
Add support for db-audit logging
Browse files Browse the repository at this point in the history
  • Loading branch information
aquatiko committed Mar 4, 2024
1 parent 7182b7a commit f8a7f17
Show file tree
Hide file tree
Showing 12 changed files with 523 additions and 7 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
credentials

ybm
ybm-cli
6 changes: 0 additions & 6 deletions cmd/backup_shedule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,9 @@ var _ = Describe("BackupSchedules", func() {
Expect(err).NotTo(HaveOccurred())
session.Wait(2)
o := string(session.Out.Contents()[:])

fmt.Println(o)
expected := `Time Interval(days) Days of the Week Backup Start Time Retention Period(days) State
NA Su,We,Fr ` + getLocalTime("2 3 * * *") + ` 8 ACTIVE` + "\n"
Expect(o).Should(Equal(expected))
fmt.Println(expected)

session.Kill()
})
It("should return list of backup schedules with a paused schedule with incremental backups", func() {
Expand Down Expand Up @@ -151,8 +147,6 @@ NA Su,We,Fr ` + getLocalTime("2 3 * * *") + `
Expect(err).NotTo(HaveOccurred())
session.Wait(2)
o := string(session.Out.Contents()[:])

fmt.Println(o)
expected := `Time Interval(days) Incr. Interval(mins) Days of the Week Backup Start Time Retention Period(days) State
NA NA Su,We,Fr ` + getLocalTime("2 3 * * *") + ` 8 ACTIVE` + "\n"
Expect(o).Should(Equal(expected))
Expand Down
221 changes: 221 additions & 0 deletions cmd/db_audit_logs_exporter/db_audit_logs_exporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// Licensed to Yugabyte, Inc. under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership. Yugabyte
// licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package db_audit_logs_exporter

import (
"fmt"
"os"
"strconv"
"strings"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
ybmAuthClient "github.com/yugabyte/ybm-cli/internal/client"
"github.com/yugabyte/ybm-cli/internal/formatter"
openapi "github.com/yugabyte/yugabytedb-managed-go-client-internal"
ybmclient "github.com/yugabyte/yugabytedb-managed-go-client-internal"
)

var DbAuditLogsExporterCmd = &cobra.Command{
Use: "db-audit-logs-exporter",
Short: "Manage DB Audit Logs",
Long: "Manage DB Audit Logs",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}

var assignDbAuditLogsExporterCmd = &cobra.Command{
Use: "assign",
Short: "Assign DB Audit",
Long: "Assign DB Audit Logs to a Cluster",
Run: func(cmd *cobra.Command, args []string) {

clusterId, _ := cmd.Flags().GetString("cluster-id")
telemetryProviderId, _ := cmd.Flags().GetString("telemetry-provider-id")
ysqlConfig, _ := cmd.Flags().GetStringToString("ysql-config")
statement_classes, _ := cmd.Flags().GetString("statement_classes")

dbAuditLogsExporterSpec, err := setDbAuditLogsExporterSpec(ysqlConfig, statement_classes, telemetryProviderId)

if err != nil {
logrus.Fatalf(err.Error())
}

authApi, err := ybmAuthClient.NewAuthApiClient()
if err != nil {
logrus.Fatalf(ybmAuthClient.GetApiErrorDetails(err))
}
authApi.GetInfo("", "")

resp, r, err := authApi.AssignDbAuditLogsExporterConfig(clusterId).DbAuditExporterConfigSpec(*dbAuditLogsExporterSpec).Execute()

if err != nil {
logrus.Debugf("Full HTTP response: %v", r)
logrus.Fatalf(ybmAuthClient.GetApiErrorDetails(err))
}

dbAuditTelemetryProviderId := resp.GetData().Info.Id

msg := fmt.Sprintf("The db audit exporter config %s is being created", formatter.Colorize(dbAuditTelemetryProviderId, formatter.GREEN_COLOR))

fmt.Println(msg)

dbAuditLogsExporterCtx := formatter.Context{
Output: os.Stdout,
Format: formatter.NewDbAuditLogsExporterFormat(viper.GetString("output")),
}

formatter.DbAuditLogsExporterWrite(dbAuditLogsExporterCtx, []openapi.DbAuditExporterConfigurationData{resp.GetData()})
},
}

var listDbAuditLogsExporterCmd = &cobra.Command{
Use: "list",
Short: "List DB Audit Logs Export Config",
Long: "List DB Audit Logs Export Config",
Run: func(cmd *cobra.Command, args []string) {
authApi, err := ybmAuthClient.NewAuthApiClient()
if err != nil {
logrus.Fatalf(ybmAuthClient.GetApiErrorDetails(err))
}
authApi.GetInfo("", "")

clusterId, _ := cmd.Flags().GetString("cluster-id")

resp, r, err := authApi.ListDbAuditLogsExportConfigs(clusterId).Execute()

if err != nil {
logrus.Debugf("Full HTTP response: %v", r)
logrus.Fatalf(ybmAuthClient.GetApiErrorDetails(err))
}

dbAuditLogsExporterCtx := formatter.Context{
Output: os.Stdout,
Format: formatter.NewDbAuditLogsExporterFormat(viper.GetString("output")),
}

if len(resp.GetData()) < 1 {
fmt.Println("No DB Audit Logs Exporter found")
return
}

formatter.DbAuditLogsExporterWrite(dbAuditLogsExporterCtx, resp.GetData())
},
}

func init() {
DbAuditLogsExporterCmd.AddCommand(assignDbAuditLogsExporterCmd)
assignDbAuditLogsExporterCmd.Flags().SortFlags = false
assignDbAuditLogsExporterCmd.Flags().String("telemetry-provider-id", "", "[REQUIRED] The ID of the telemetry provider")
assignDbAuditLogsExporterCmd.MarkFlagRequired("telemetry-provider-id")
assignDbAuditLogsExporterCmd.Flags().StringToString("ysql-config", nil, `[REQUIRED] The ysql config to setup DB auditting
Please provide key value pairs as follows:
log_catalog=<boolean>,log_level=<LOG_LEVEL>`)
assignDbAuditLogsExporterCmd.MarkFlagRequired("ysql-config")
assignDbAuditLogsExporterCmd.Flags().String("statement_classes", "", `[REQUIRED] The ysql config statement classes
Please provide key value pairs as follows:
statement_classes=READ,WRITE,MISC`)
assignDbAuditLogsExporterCmd.MarkFlagRequired("statement_classes")
assignDbAuditLogsExporterCmd.Flags().String("cluster-id", "", "[REQUIRED] The cluster ID to assign DB auditting")
assignDbAuditLogsExporterCmd.MarkFlagRequired("cluster-id")

DbAuditLogsExporterCmd.AddCommand(listDbAuditLogsExporterCmd)
listDbAuditLogsExporterCmd.Flags().SortFlags = false
listDbAuditLogsExporterCmd.Flags().String("cluster-id", "", "[REQUIRED] The cluster ID to list DB audit export config")
listDbAuditLogsExporterCmd.MarkFlagRequired("cluster-id")
}

func setDbAuditLogsExporterSpec(ysqlConfigMap map[string]string, statementClasses string, telemetryProviderId string) (*ybmclient.DbAuditExporterConfigSpec, error) {
log_catalog := ysqlConfigMap["log_catalog"]
log_client := ysqlConfigMap["log_client"]
log_level := ysqlConfigMap["log_level"]
log_parameter := ysqlConfigMap["log_parameter"]
log_relation := ysqlConfigMap["log_relation"]
log_statement_once := ysqlConfigMap["log_statement_once"]

var statement_classes_enum []ybmclient.DbAuditYsqlStatmentClassesEnum

if statementClasses != "" {
for _, statement := range strings.Split(statementClasses, ",") {
enumVal, err := ybmclient.NewDbAuditYsqlStatmentClassesEnumFromValue(statement)
if err != nil {
return nil, err
}
statement_classes_enum = append(statement_classes_enum, *enumVal)
}
}

log_settings := ybmclient.NewDbAuditYsqlLogSettingsWithDefaults()

if log_catalog != "" {
catalog, err := strconv.ParseBool(log_catalog)
if err != nil {
return nil, err
}
log_settings.SetLogCatalog(catalog)
}

if log_client != "" {
client, err := strconv.ParseBool(log_client)
if err != nil {
return nil, err
}
log_settings.SetLogClient(client)
}

if log_level != "" {
level, err := ybmclient.NewDbAuditLogLevelEnumFromValue(log_level)
if err != nil {
return nil, err
}
log_settings.SetLogLevel(*level)
}

if log_parameter != "" {
parameter, err := strconv.ParseBool(log_parameter)
if err != nil {
return nil, err
}
log_settings.SetLogParameter(parameter)
}

if log_relation != "" {
relation, err := strconv.ParseBool(log_relation)
if err != nil {
return nil, err
}
log_settings.SetLogRelation(relation)
}

if log_statement_once != "" {
statement_once, err := strconv.ParseBool(log_statement_once)
if err != nil {
return nil, err
}
log_settings.SetLogStatementOnce(statement_once)
}

ysqlConfig := ybmclient.NewDbAuditYsqlExportConfigWithDefaults()
if len(statement_classes_enum) > 0 {
ysqlConfig.SetStatementClasses(statement_classes_enum)
}

ysqlConfig.SetLogSettings(*log_settings)

return ybmclient.NewDbAuditExporterConfigSpec(*ysqlConfig, telemetryProviderId), nil
}
121 changes: 121 additions & 0 deletions cmd/db_audit_logs_exporter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Licensed to Yugabyte, Inc. under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership. Yugabyte
// licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package cmd_test

import (
"fmt"
"net/http"
"os"
"os/exec"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
"github.com/onsi/gomega/ghttp"
openapi "github.com/yugabyte/yugabytedb-managed-go-client-internal"
)

var _ = Describe("Db Audit", func() {

var (
server *ghttp.Server
statusCode int
args []string
responseAccount openapi.AccountListResponse
responseProject openapi.AccountListResponse
responseDbAudit openapi.DbAuditExporterConfigResponse
responseDbAuditList openapi.DbAuditExporterConfigListResponse
)

BeforeEach(func() {
args = os.Args
os.Args = []string{}
var err error
server, err = newGhttpServer(responseAccount, responseProject)
Expect(err).ToNot(HaveOccurred())
os.Setenv("YBM_HOST", fmt.Sprintf("http://%s", server.Addr()))
os.Setenv("YBM_APIKEY", "test-token")
})

Context("When associating DB Audit config", func() {
It("should associate cluster with DB Audit", func() {
statusCode = 200
err := loadJson("./test/fixtures/db-audit-data.json", &responseDbAudit)
Expect(err).ToNot(HaveOccurred())
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest(http.MethodPost, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/projects/78d4459c-0f45-47a5-899a-45ddf43eba6e/clusters/7bb68af6-0875-42e0-8665-dcf634ed9fd1/db-audit-log-exporter-configs"),
ghttp.RespondWithJSONEncodedPtr(&statusCode, responseDbAudit),
),
)
cmd := exec.Command(compiledCLIPath, "db-audit-logs-exporter", "assign", "--cluster-id", "7bb68af6-0875-42e0-8665-dcf634ed9fd1", "--telemetry-provider-id", "7c07c103-e3b2-48b6-ac30-764e9b5275e1", "--ysql-config", "log_catalog=true,log_client=false,log_level=INFO,log_parameter=true", "--statement_classes", "READ,WRITE")
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
session.Wait(2)
Expect(session.Out).Should(gbytes.Say(`The db audit exporter config 9e3fabbc-849c-4a77-bdb2-9422e712e7dc is being created
ID Date Created Cluster ID Telemetry Provider ID State Ysql Config
9e3fabbc-849c-4a77-bdb2-9422e712e7dc 2024-02-27T06:30:51.304Z 7bb68af6-0875-42e0-8665-dcf634ed9fd1 7c07c103-e3b2-48b6-ac30-764e9b5275e1 ACTIVE {\"log_settings\":{\"log_catalog\":true,\"log_client\":true,\"log_level\":\"LOG\",\"log_parameter\":false,\"log_relation\":false,\"log_statement_once\":false},\"statement_classes\":\[\"READ\",\"WRITE\"]}`))
session.Kill()
})
It("should return required field name and type when not set", func() {

cmd := exec.Command(compiledCLIPath, "db-audit-logs-exporter", "assign")
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
session.Wait(2)
Expect(session.Err).Should(gbytes.Say(`\bError: required flag\(s\) "telemetry-provider-id", "ysql-config", "statement_classes", "cluster-id" not set\b`))
session.Kill()
})
})

Context("When listing db audit exporter config", func() {
It("should return the list of config", func() {
statusCode = 200
err := loadJson("./test/fixtures/list-db-audit.json", &responseDbAuditList)
Expect(err).ToNot(HaveOccurred())
server.AppendHandlers(
ghttp.CombineHandlers(
ghttp.VerifyRequest(http.MethodGet, "/api/public/v1/accounts/340af43a-8a7c-4659-9258-4876fd6a207b/projects/78d4459c-0f45-47a5-899a-45ddf43eba6e/clusters/7bb68af6-0875-42e0-8665-dcf634ed9fd1/db-audit-log-exporter-configs"),
ghttp.RespondWithJSONEncodedPtr(&statusCode, responseDbAuditList),
),
)
cmd := exec.Command(compiledCLIPath, "db-audit-logs-exporter", "list", "--cluster-id", "7bb68af6-0875-42e0-8665-dcf634ed9fd1")
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
session.Wait(2)
Expect(session.Out).Should(gbytes.Say(`ID Date Created Cluster ID Telemetry Provider ID State Ysql Config
9e3fabbc-849c-4a77-bdb2-9422e712e7dc 2024-02-27T06:30:51.304Z 7bb68af6-0875-42e0-8665-dcf634ed9fd1 7c07c103-e3b2-48b6-ac30-764e9b5275e1 ACTIVE {\"log_settings\":{\"log_catalog\":true,\"log_client\":true,\"log_level\":\"LOG\",\"log_parameter\":false,\"log_relation\":false,\"log_statement_once\":false},\"statement_classes\":\[\"READ\",\"WRITE\"]}`))
session.Kill()
})
It("should return required field name and type when not set", func() {

cmd := exec.Command(compiledCLIPath, "db-audit-logs-exporter", "list")
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
session.Wait(2)
Expect(session.Err).Should(gbytes.Say("(?m:Error: required flag\\(s\\) \"cluster-id\" not set$)"))
session.Kill()
})

})

AfterEach(func() {
os.Args = args
server.Close()
})

})
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/yugabyte/ybm-cli/cmd/cdc"
"github.com/yugabyte/ybm-cli/cmd/cluster"
"github.com/yugabyte/ybm-cli/cmd/metrics_exporter"
"github.com/yugabyte/ybm-cli/cmd/db_audit_logs_exporter"
"github.com/yugabyte/ybm-cli/cmd/nal"
"github.com/yugabyte/ybm-cli/cmd/permission"
"github.com/yugabyte/ybm-cli/cmd/region"
Expand Down Expand Up @@ -135,6 +136,7 @@ func init() {
rootCmd.AddCommand(api_key.ApiKeyCmd)
rootCmd.AddCommand(user.UserCmd)
rootCmd.AddCommand(metrics_exporter.MetricsExporterCmd)
rootCmd.AddCommand(db_audit_logs_exporter.DbAuditLogsExporterCmd)
util.AddCommandIfFeatureFlag(rootCmd, tools.ToolsCmd, util.TOOLS)
util.AddCommandIfFeatureFlag(rootCmd, cdc.CdcCmd, util.CDC)

Expand Down
Loading

0 comments on commit f8a7f17

Please sign in to comment.