Skip to content

Commit

Permalink
[skunkworks] Auditing contract testing
Browse files Browse the repository at this point in the history
Signed-off-by: jose.vazquez <jose.vazquez@mongodb.com>
  • Loading branch information
josvazg committed May 14, 2024
1 parent fbbe7e0 commit 439dd3a
Show file tree
Hide file tree
Showing 20 changed files with 716 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/cloud-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ jobs:
with:
forked: ${{ inputs.forked }}

contract-tests:
needs: allowed
uses: ./.github/workflows/test-contract.yml
secrets: inherit

e2e-tests:
needs: allowed
uses: ./.github/workflows/test-e2e.yml
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/test-contract.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Contract Tests

on:
workflow_call:
workflow_dispatch:

jobs:
contract:
name: Contract Tests
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
ref: ${{github.event.pull_request.head.sha}}

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: "${{ github.workspace }}/go.mod"
cache: false

- name: Create k8s Kind Cluster
uses: helm/kind-action@v1.10.0
with:
version: v0.22.0
config: test/helper/e2e/config/kind.yaml
node_image: kindest/node:v1.29.2

- name: Run Contract Testing
env:
AKO_CONTRACT_TEST: 1
MCLI_OPS_MANAGER_URL: https://cloud-qa.mongodb.com
MCLI_ORG_ID: ${{ secrets.ATLAS_ORG_ID }}
MCLI_PUBLIC_API_KEY: ${{ secrets.ATLAS_PUBLIC_KEY }}
MCLI_PRIVATE_API_KEY: ${{ secrets.ATLAS_PRIVATE_KEY }}
run: make contract-tests
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,8 @@ jobs:
secrets: inherit
with:
forked: ${{ github.event.repository.full_name != github.event.pull_request.head.repo.full_name }}

contract-tests: # TODO: remove shortcut
needs: run-tests
uses: ./.github/workflows/test-contract.yml
secrets: inherit
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -498,3 +498,8 @@ gen-sdlc-checklist: envsubst docker-sbom ## Generate the SDLC checklist
.PHONY: clear-e2e-leftovers
clear-e2e-leftovers: ## Clear the e2e test leftovers quickly
git restore bundle* config deploy

.PHONY: contract-tests
contract-tests: ## Run contract tests
go clean -testcache
AKO_CONTRACT_TEST=1 go test -race -cover ./test/contract/...
47 changes: 47 additions & 0 deletions internal/translayer/auditing/auditing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package auditing

import (
"context"
"fmt"

"go.mongodb.org/atlas-sdk/v20231115008/admin"
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/types"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translayer"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/atlas"
)

type Service interface {
Get(ctx context.Context, projectID string) (*Auditing, error)
Set(ctx context.Context, projectID string, auditing *Auditing) error
}

type service struct {
admin.AuditingApi
}

func NewService(ctx context.Context, provider atlas.Provider, secretRef *types.NamespacedName, log *zap.SugaredLogger) (Service, error) {
client, err := translayer.NewVersionedClient(ctx, provider, secretRef, log)
if err != nil {
return nil, err
}
return NewFromAuditingAPI(client.AuditingApi), nil
}

func NewFromAuditingAPI(api admin.AuditingApi) *service {
return &service{AuditingApi: api}
}

func (s *service) Get(ctx context.Context, projectID string) (*Auditing, error) {
auditLog, _, err := s.AuditingApi.GetAuditingConfiguration(ctx, projectID).Execute()
if err != nil {
return nil, fmt.Errorf("failed to get audit log from Atlas: %w", err)
}
return fromAtlas(auditLog)
}

func (s *service) Set(ctx context.Context, projectID string, auditing *Auditing) error {
_, _, err := s.AuditingApi.UpdateAuditingConfiguration(ctx, projectID, toAtlas(auditing)).Execute()
return err
}
56 changes: 56 additions & 0 deletions internal/translayer/auditing/translate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package auditing

import (
"fmt"

"go.mongodb.org/atlas-sdk/v20231115008/admin"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer"
)

type AuditingConfigType string

const (
None AuditingConfigType = "NONE"
FilterBuilder AuditingConfigType = "FILTER_BUILDER"
FilterJSON AuditingConfigType = "FILTER_JSON"
)

type Auditing struct {
Enabled bool
AuditAuthorizationSuccess bool
ConfigurationType AuditingConfigType
AuditFilter string
}

func toAtlas(auditing *Auditing) *admin.AuditLog {
return &admin.AuditLog{
Enabled: pointer.MakePtr(auditing.Enabled),
AuditAuthorizationSuccess: pointer.MakePtr(auditing.AuditAuthorizationSuccess),
AuditFilter: pointer.MakePtr(auditing.AuditFilter),
// ConfigurationType is not set on the PATCH operation to Atlas
}
}

func fromAtlas(auditLog *admin.AuditLog) (*Auditing, error) {
cfgType, err := configTypeFromAtlas(auditLog.ConfigurationType)
if err != nil {
return nil, err
}
return &Auditing{
Enabled: pointer.GetOrDefault(auditLog.Enabled, false),
AuditAuthorizationSuccess: pointer.GetOrDefault(auditLog.AuditAuthorizationSuccess, false),
ConfigurationType: cfgType,
AuditFilter: pointer.GetOrDefault(auditLog.AuditFilter, ""),
}, nil
}

func configTypeFromAtlas(configType *string) (AuditingConfigType, error) {
ct := pointer.GetOrDefault(configType, string(None))
switch ct {
case string(None), string(FilterBuilder), string(FilterJSON):
return AuditingConfigType(ct), nil
default:
return AuditingConfigType(ct), fmt.Errorf("unsupported Auditing Config type %q", ct)
}
}
20 changes: 20 additions & 0 deletions internal/translayer/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package translayer

import (
"context"
"fmt"

"go.mongodb.org/atlas-sdk/v20231115008/admin"
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/types"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/atlas"
)

func NewVersionedClient(ctx context.Context, provider atlas.Provider, secretRef *types.NamespacedName, log *zap.SugaredLogger) (*admin.APIClient, error) {
apiClient, _, err := provider.SdkClient(ctx, secretRef, log)
if err != nil {
return nil, fmt.Errorf("failed to instantiate Versioned Atlas client: %w", err)
}
return apiClient, nil
}
144 changes: 144 additions & 0 deletions test/contract/auditing/auditing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package auditing

import (
"context"
_ "embed"
"log"
"testing"
"time"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translayer/auditing"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/control"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/launcher"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/contract"
)

//go:embed test.yml
var testYml string

const (
testVersion = "2.1.0"
)

func TestMain(m *testing.M) {
if !control.Enabled("AKO_CONTRACT_TEST") {
log.Printf("Skipping contract test as AKO_CONTRACT_TEST is unset")
return
}
l := launcher.NewFromEnv(testVersion)
l.Launch(
testYml,
launcher.WaitReady("atlasprojects/my-project", 30*time.Second))
if !control.Enabled("SKIP_CLEANUP") { // allow to reuse Atlas resources for local tests
defer l.Cleanup()
}
m.Run()
}

func TestDefaultAuditingGet(t *testing.T) {
testProjectID := mustReadProjectID()
ctx := context.Background()
as := auditing.NewFromAuditingAPI(contract.MustVersionedClient(t, ctx).AuditingApi)

result, err := as.Get(ctx, testProjectID)

require.NoError(t, err)
result.ConfigurationType = "" // Do not expect the returned cfg type to match
if result.AuditFilter == "{}" {
// Support re-runs, as we cannot get the filter back to empty
result.AuditFilter = ""
}
assert.Equal(t, defaultAtlasAuditing(), result)
}

func defaultAtlasAuditing() *auditing.Auditing {
return &auditing.Auditing{
Enabled: false,
AuditAuthorizationSuccess: false,
AuditFilter: "",
}
}

func TestSyncs(t *testing.T) {
testCases := []struct {
title string
auditing *auditing.Auditing
}{
{
title: "Just enabled",
auditing: &auditing.Auditing{
Enabled: true,
AuditAuthorizationSuccess: false,
AuditFilter: "{}", // must sent empty JSON to overwrite previous state
},
},
{
title: "Auth success logs as well",
auditing: &auditing.Auditing{
Enabled: true,
AuditAuthorizationSuccess: true,
AuditFilter: "{}",
},
},
{
title: "With a filter",
auditing: &auditing.Auditing{
Enabled: true,
AuditAuthorizationSuccess: false,
AuditFilter: `{"atype":"authenticate"}`,
},
},
{
title: "With a filter and success logs",
auditing: &auditing.Auditing{
Enabled: true,
AuditAuthorizationSuccess: true,
AuditFilter: `{"atype":"authenticate"}`,
},
},
{
title: "All set but disabled",
auditing: &auditing.Auditing{
Enabled: false,
AuditAuthorizationSuccess: true,
AuditFilter: `{"atype":"authenticate"}`,
},
},
{
title: "Default (disabled) case",
auditing: &auditing.Auditing{
Enabled: false,
AuditAuthorizationSuccess: false,
AuditFilter: "{}",
},
},
}
testProjectID := mustReadProjectID()
ctx := context.Background()
as := auditing.NewFromAuditingAPI(contract.MustVersionedClient(t, ctx).AuditingApi)

for _, tc := range testCases {
t.Run(tc.title, func(t *testing.T) {
err := as.Set(ctx, testProjectID, tc.auditing)
require.NoError(t, err)

result, err := as.Get(ctx, testProjectID)
require.NoError(t, err)
result.ConfigurationType = "" // Do not expect the returned cfg type to match
assert.Equal(t, tc.auditing, result)
})
}
}

func mustReadProjectID() string {
l := launcher.NewFromEnv(testVersion)
output, err := l.Kubectl("get", "atlasprojects/my-project", "-o=jsonpath={.status.id}")
if err != nil {
log.Fatalf("Failed to get test project id: %v", err)
}
return output
}
6 changes: 6 additions & 0 deletions test/contract/auditing/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: atlas.mongodb.com/v1
kind: AtlasProject
metadata:
name: my-project
spec:
name: Test Atlas Operator Project
26 changes: 26 additions & 0 deletions test/contract/contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package contract

import (
"context"
"os"
"testing"

"go.mongodb.org/atlas-sdk/v20231115008/admin"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/atlas"
)

func NewVersionedClient(ctx context.Context) (*admin.APIClient, error) {
domain := os.Getenv("MCLI_OPS_MANAGER_URL")
pubKey := os.Getenv("MCLI_PUBLIC_API_KEY")
prvKey := os.Getenv("MCLI_PRIVATE_API_KEY")
return atlas.NewClient(domain, pubKey, prvKey)
}

func MustVersionedClient(t *testing.T, ctx context.Context) *admin.APIClient {
client, err := NewVersionedClient(ctx)
if err != nil {
t.Fatalf("Failed to get Atlas versioned client: %v", err)
}
return client
}
4 changes: 1 addition & 3 deletions test/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ var (
)

func TestE2e(t *testing.T) {
if !control.Enabled("AKO_E2E_TEST") {
t.Skip("Skipping e2e tests, AKO_E2E_TEST is not set")
}
control.SkipTestUnless(t, "AKO_E2E_TEST")

RegisterFailHandler(Fail)
RunSpecs(t, "Atlas Operator E2E Test Suite")
Expand Down

0 comments on commit 439dd3a

Please sign in to comment.