From fd01c7f8c92eeebe7465561e7a978b39f26d7854 Mon Sep 17 00:00:00 2001 From: Ross Date: Thu, 16 Jun 2022 11:17:08 +0100 Subject: [PATCH] feat(api): Implement GcpGkeAudit CloudAccount interface (#821) * feat(api): Implement GcpGkeAudit CloudAccount interface Signed-off-by: Ross * chore(api): Fix failing gke unit tests Signed-off-by: Ross --- .../cloud-accounts/gcp-gke-audit/main.go | 65 +++++ api/cloud_accounts.go | 2 + api/cloud_accounts_gcp_gke_audit.go | 62 +++++ api/cloud_accounts_gcp_gke_audit_test.go | 260 ++++++++++++++++++ 4 files changed, 389 insertions(+) create mode 100644 api/_examples/cloud-accounts/gcp-gke-audit/main.go create mode 100644 api/cloud_accounts_gcp_gke_audit.go create mode 100644 api/cloud_accounts_gcp_gke_audit_test.go diff --git a/api/_examples/cloud-accounts/gcp-gke-audit/main.go b/api/_examples/cloud-accounts/gcp-gke-audit/main.go new file mode 100644 index 000000000..8e79126bd --- /dev/null +++ b/api/_examples/cloud-accounts/gcp-gke-audit/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/lacework/go-sdk/api" +) + +func main() { + // TODO @afiune maybe think about a way to inject CI credentials and + // run these examples as part of our CI pipelines + lacework, err := api.NewClient(os.Getenv("LW_ACCOUNT"), + api.WithSubaccount(os.Getenv("LW_SUBACCOUNT")), + api.WithApiKeys(os.Getenv("LW_API_KEY"), os.Getenv("LW_API_SECRET")), + api.WithApiV2(), + ) + if err != nil { + log.Fatal(err) + } + + res, err := lacework.V2.CloudAccounts.List() + if err != nil { + log.Fatal(err) + } + + for _, account := range res.Data { + support := "Unsupported" + switch account.Type { + case api.GcpGkeAuditCloudAccount.String(): + support = "Supported" + } + + // Output: INTEGRATION-GUID:INTEGRATION-TYPE:[Supported|Unsupported] + fmt.Printf("%s:%s:%s\n", account.IntgGuid, account.Type, support) + } + + gcpGkeAuditData := api.GcpGkeAuditData{ + Credentials: api.GcpGkeAuditCredentials{ + ClientEmail: "ross.moles@lacework.net", + ClientId: "0123456789", + PrivateKey: "", + PrivateKeyId: "", + }, + IntegrationType: "Project", + OrganizationId: "OrgId", + ProjectId: "ProjectMcProjectFace", + SubscriptionName: "projects/ProjectMcProjectFace/subscriptions/SubscribeyMcSubscribeFace", + } + + gcpGkeAuditCloudAccount := api.NewCloudAccount( + "cloud-from-golang", + api.GcpGkeAuditCloudAccount, + gcpGkeAuditData, + ) + + gcpGkeAuditResponse, err := lacework.V2.CloudAccounts.Create(gcpGkeAuditCloudAccount) + if err != nil { + log.Fatal(err) + } + + // Output: GcpGkeAudit Cloud Account created: THE-INTEGRATION-GUID + fmt.Printf("Cloud Account created: %s", gcpGkeAuditResponse.Data.IntgGuid) +} diff --git a/api/cloud_accounts.go b/api/cloud_accounts.go index 75cf505f6..d91fea27e 100644 --- a/api/cloud_accounts.go +++ b/api/cloud_accounts.go @@ -93,6 +93,7 @@ const ( AzureCfgCloudAccount GcpAtSesCloudAccount GcpCfgCloudAccount + GcpGkeAuditCloudAccount ) // CloudAccountTypes is the list of available Cloud Account integration types @@ -107,6 +108,7 @@ var CloudAccountTypes = map[cloudAccountType]string{ AzureCfgCloudAccount: "AzureCfg", GcpAtSesCloudAccount: "GcpAtSes", GcpCfgCloudAccount: "GcpCfg", + GcpGkeAuditCloudAccount: "GcpGkeAudit", } // String returns the string representation of a Cloud Account integration type diff --git a/api/cloud_accounts_gcp_gke_audit.go b/api/cloud_accounts_gcp_gke_audit.go new file mode 100644 index 000000000..c17086761 --- /dev/null +++ b/api/cloud_accounts_gcp_gke_audit.go @@ -0,0 +1,62 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed 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 api + +// GetGcpGkeAudit gets a single GcpGkeAudit integration matching the provided integration guid +func (svc *CloudAccountsService) GetGcpGkeAudit(guid string) ( + response GcpGkeAuditIntegrationResponse, + err error, +) { + err = svc.get(guid, &response) + return +} + +// UpdateGcpGkeAudit updates a single GcpGkeAudit integration on the Lacework Server +func (svc *CloudAccountsService) UpdateGcpGkeAudit(data CloudAccount) ( + response GcpGkeAuditIntegrationResponse, + err error, +) { + err = svc.update(data.ID(), data, &response) + return +} + +type GcpGkeAuditIntegrationResponse struct { + Data GcpGkeAuditIntegration `json:"data"` +} + +type GcpGkeAuditIntegration struct { + v2CommonIntegrationData + Data GcpGkeAuditData `json:"data"` +} + +type GcpGkeAuditData struct { + Credentials GcpGkeAuditCredentials `json:"credentials"` + IntegrationType string `json:"integrationType"` + // OrganizationId is optional for a project level integration, therefore we omit if empty + OrganizationId string `json:"organizationId,omitempty"` + ProjectId string `json:"projectId"` + SubscriptionName string `json:"subscriptionName"` +} + +type GcpGkeAuditCredentials struct { + ClientId string `json:"clientId"` + ClientEmail string `json:"clientEmail"` + PrivateKeyId string `json:"PrivateKeyID"` + PrivateKey string `json:"PrivateKey"` +} diff --git a/api/cloud_accounts_gcp_gke_audit_test.go b/api/cloud_accounts_gcp_gke_audit_test.go new file mode 100644 index 000000000..e8d5d8063 --- /dev/null +++ b/api/cloud_accounts_gcp_gke_audit_test.go @@ -0,0 +1,260 @@ +// +// Author:: Salim Afiune Maya () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed 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 api_test + +import ( + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/lacework/go-sdk/api" + "github.com/lacework/go-sdk/internal/intgguid" + "github.com/lacework/go-sdk/internal/lacework" +) + +func TestCloudAccountsGcpGkeAuditProjectLevelGet(t *testing.T) { + var ( + intgGUID = intgguid.New() + apiPath = fmt.Sprintf("CloudAccounts/%s", intgGUID) + fakeServer = lacework.MockServer() + ) + fakeServer.UseApiV2() + fakeServer.MockToken("TOKEN") + defer fakeServer.Close() + + fakeServer.MockAPI(apiPath, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method, "GetGcpGkeAudit() should be a GET method") + fmt.Fprintf(w, generateCloudAccountResponse(gcpGkeAuditProjectLevelCloudAccount(intgGUID))) + }) + + c, err := api.NewClient("test", + api.WithApiV2(), + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + response, err := c.V2.CloudAccounts.GetGcpGkeAudit(intgGUID) + assert.Nil(t, err) + assert.NotNil(t, response) + assert.Equal(t, intgGUID, response.Data.IntgGuid) + assert.Equal(t, "integration_name", response.Data.Name) + assert.True(t, response.Data.State.Ok) + assert.Equal(t, "0123456789", response.Data.Data.Credentials.ClientId) + assert.Equal(t, "ross.moles@lacework.net", response.Data.Data.Credentials.ClientEmail) + assert.Empty(t, response.Data.Data.Credentials.PrivateKeyId) + assert.Empty(t, response.Data.Data.Credentials.PrivateKey) + assert.Equal(t, "Project", response.Data.Data.IntegrationType) + assert.Equal(t, "ProjectMcProjectFace", response.Data.Data.ProjectId) + assert.Equal( + t, + "projects/ProjectMcProjectFace/subscriptions/SubscribeyMcSubscribeFace", + response.Data.Data.SubscriptionName, + ) +} + +func TestCloudAccountsGcpGkeAuditOrganizationLevelGet(t *testing.T) { + var ( + intgGUID = intgguid.New() + apiPath = fmt.Sprintf("CloudAccounts/%s", intgGUID) + fakeServer = lacework.MockServer() + ) + fakeServer.UseApiV2() + fakeServer.MockToken("TOKEN") + defer fakeServer.Close() + + fakeServer.MockAPI(apiPath, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "GET", r.Method, "GetGcpGkeAudit() should be a GET method") + fmt.Fprintf(w, generateCloudAccountResponse(gcpGkeAuditOrganizationLevelCloudAccount(intgGUID))) + }) + + c, err := api.NewClient("test", + api.WithApiV2(), + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + response, err := c.V2.CloudAccounts.GetGcpGkeAudit(intgGUID) + assert.Nil(t, err) + assert.NotNil(t, response) + assert.Equal(t, intgGUID, response.Data.IntgGuid) + assert.Equal(t, "integration_name", response.Data.Name) + assert.True(t, response.Data.State.Ok) + assert.Equal(t, "0123456789", response.Data.Data.Credentials.ClientId) + assert.Equal(t, "ross.moles@lacework.net", response.Data.Data.Credentials.ClientEmail) + assert.Empty(t, response.Data.Data.Credentials.PrivateKeyId) + assert.Empty(t, response.Data.Data.Credentials.PrivateKey) + assert.Equal(t, "Project", response.Data.Data.IntegrationType) + assert.Equal(t, "OrgMcOrgFace", response.Data.Data.OrganizationId) + assert.Equal(t, "ProjectMcProjectFace", response.Data.Data.ProjectId) + assert.Equal( + t, + "projects/ProjectMcProjectFace/subscriptions/SubscribeyMcSubscribeFace", + response.Data.Data.SubscriptionName, + ) +} + +func TestCloudAccountsGcpGkeAuditProjectLevelUpdate(t *testing.T) { + var ( + intgGUID = intgguid.New() + apiPath = fmt.Sprintf("CloudAccounts/%s", intgGUID) + fakeServer = lacework.MockServer() + ) + fakeServer.UseApiV2() + fakeServer.MockToken("TOKEN") + defer fakeServer.Close() + + fakeServer.MockAPI(apiPath, func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "PATCH", r.Method, "UpdateGcpGkeAudit() should be a PATCH method") + + if assert.NotNil(t, r.Body) { + body := httpBodySniffer(r) + assert.Contains(t, body, intgGUID, "INTG_GUID missing") + assert.Contains(t, body, "integration_name", "cloud account name is missing") + assert.Contains(t, body, "GcpGkeAudit", "wrong cloud account type") + assert.Contains(t, body, "ross.moles@lacework.net", "wrong client email") + assert.Contains(t, body, "0123456789", "wrong client email") + assert.Contains(t, body, "\"integrationType\":\"Project", "wrong integration type") + assert.Contains(t, body, "projectId\":\"ProjectMcProjectFace", "wrong project id") + assert.Contains( + t, + body, + "projects/ProjectMcProjectFace/subscriptions/SubscribeyMcSubscribeFace", + "wrong subscription name") + assert.Contains(t, body, "enabled\":1", "cloud account is not enabled") + } + + fmt.Fprintf(w, generateCloudAccountResponse(gcpGkeAuditProjectLevelCloudAccount(intgGUID))) + }) + + c, err := api.NewClient("test", + api.WithApiV2(), + api.WithToken("TOKEN"), + api.WithURL(fakeServer.URL()), + ) + assert.Nil(t, err) + + cloudAccount := api.NewCloudAccount("integration_name", + api.GcpGkeAuditCloudAccount, + api.GcpGkeAuditData{ + Credentials: api.GcpGkeAuditCredentials{ + ClientEmail: "ross.moles@lacework.net", + ClientId: "0123456789", + PrivateKey: "", + PrivateKeyId: "", + }, + IntegrationType: "Project", + ProjectId: "ProjectMcProjectFace", + SubscriptionName: "projects/ProjectMcProjectFace/subscriptions/" + + "SubscribeyMcSubscribeFace", + }, + ) + assert.Equal(t, "integration_name", cloudAccount.Name, "GcpGkeAudit cloud account name mismatch") + assert.Equal(t, "GcpGkeAudit", cloudAccount.Type, "a new GcpGkeAudit cloud account should match its type") + assert.Equal(t, 1, cloudAccount.Enabled, "a new GcpGkeAudit cloud account should be enabled") + cloudAccount.IntgGuid = intgGUID + + response, err := c.V2.CloudAccounts.UpdateGcpGkeAudit(cloudAccount) + assert.Nil(t, err) + assert.NotNil(t, response) + assert.Equal(t, intgGUID, response.Data.IntgGuid) + assert.Equal(t, + "projects/ProjectMcProjectFace/subscriptions/SubscribeyMcSubscribeFace", + response.Data.Data.SubscriptionName) +} + +func gcpGkeAuditProjectLevelCloudAccount(id string) string { + return ` + { + "createdOrUpdatedBy": "ross.moles@lacework.net", + "createdOrUpdatedTime": "2021-06-01T19:28:00.092Z", + "enabled": 1, + "intgGuid": "` + id + `", + "isOrg": 0, + "name": "integration_name", + "state": { + "details": { + "decodeNtfn": "OK", + "lastMsgRxTime": 1655136633387, + "logFileGet": "OK", + "noData": false, + "queueDel": "OK", + "queueRx": "OK" + }, + "lastSuccessfulTime": 1655136633387, + "lastUpdatedTime": 1655136633387, + "ok": true + }, + "type": "GcpGkeAudit", + "data": { + "credentials": { + "clientEmail": "ross.moles@lacework.net", + "clientId": "0123456789", + "privateKey": "", + "privateKeyId": "" + }, + "integrationType": "Project", + "projectId": "ProjectMcProjectFace", + "subscriptionName": "projects/ProjectMcProjectFace/subscriptions/SubscribeyMcSubscribeFace" + } + } + ` +} + +func gcpGkeAuditOrganizationLevelCloudAccount(id string) string { + return ` + { + "createdOrUpdatedBy": "ross.moles@lacework.net", + "createdOrUpdatedTime": "2021-06-01T19:28:00.092Z", + "enabled": 1, + "intgGuid": "` + id + `", + "isOrg": 0, + "name": "integration_name", + "state": { + "details": { + "decodeNtfn": "OK", + "lastMsgRxTime": 1655136633387, + "logFileGet": "OK", + "noData": false, + "queueDel": "OK", + "queueRx": "OK" + }, + "lastSuccessfulTime": 1655136633387, + "lastUpdatedTime": 1655136633387, + "ok": true + }, + "type": "GcpGkeAudit", + "data": { + "credentials": { + "clientEmail": "ross.moles@lacework.net", + "clientId": "0123456789", + "privateKey": "", + "privateKeyId": "" + }, + "integrationType": "Project", + "organizationId": "OrgMcOrgFace", + "projectId": "ProjectMcProjectFace", + "subscriptionName": "projects/ProjectMcProjectFace/subscriptions/SubscribeyMcSubscribeFace" + } + } + ` +}