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

Create Audit Log FSM state #344

Merged
merged 31 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c05d995
Create Audit Log FSM state
mvshao Aug 14, 2024
e1af088
Create new FSM states, fix linter
mvshao Aug 19, 2024
a2d1df9
Repair linter
mvshao Aug 19, 2024
425c02f
Repair linter among whole project
mvshao Aug 19, 2024
bd3b770
Comment case for testing
mvshao Aug 19, 2024
40aca00
Add Gardenert to scheme
mvshao Aug 19, 2024
45700ed
Change k8s client
mvshao Aug 19, 2024
0715199
Remove gardenere from runtime schema
mvshao Aug 19, 2024
c4e4982
Refactor
mvshao Aug 20, 2024
c923e98
Pass additional parameter
mvshao Aug 20, 2024
b348fda
First phase of fixing tests
mvshao Aug 23, 2024
f431a3b
Second phase of fixing tests
mvshao Aug 26, 2024
ccad08b
Create generic function
mvshao Aug 26, 2024
a884581
Add testdata
mvshao Aug 26, 2024
d0c21b7
Third round of fixing controller tests
mvshao Aug 26, 2024
abb3486
Create unit tests for Audit Log state
mvshao Aug 28, 2024
57e83ad
Comment tests
mvshao Aug 28, 2024
964b2b1
Add mocks
mvshao Aug 28, 2024
9479ce7
Fix linter errors
mvshao Aug 28, 2024
e594864
Fix linter
mvshao Aug 28, 2024
d62e17a
Fix linter
mvshao Aug 28, 2024
316a37a
Add unit test for logic
mvshao Aug 28, 2024
9620513
Add unit tests
mvshao Aug 28, 2024
0ee1e98
Add new condition
mvshao Aug 29, 2024
320ddd6
Merge branch 'main' into auditlog-fsm
mvshao Aug 29, 2024
a0a0b83
Adjust controller tests
mvshao Aug 29, 2024
0902ae7
Change the type of resons in status, fix the flow
mvshao Aug 29, 2024
477fc94
Update program flow
mvshao Aug 30, 2024
cb72a1b
Fix linter
mvshao Aug 30, 2024
a207bcf
Apply review sugestiosn
mvshao Sep 2, 2024
d6b5669
Trigger GA
mvshao Sep 2, 2024
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
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o ma
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /project_workspace/manager .
COPY converter_config.json .
USER 65532:65532

ENTRYPOINT ["/manager"]
3 changes: 3 additions & 0 deletions api/v1/runtime_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ const (
ConditionReasonKubernetesAPIErr = RuntimeConditionReason("KubernetesErr")
ConditionReasonSerializationError = RuntimeConditionReason("SerializationErr")
ConditionReasonDeleted = RuntimeConditionReason("Deleted")

ConditionReasonAdministratorsConfigured = RuntimeConditionReason("AdministratorsConfigured")
Copy link
Member

Choose a reason for hiding this comment

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

I can't find the usage of it - was this addition intentional?

ConditionReasonAuditLogConfigured = RuntimeConditionReason("AuditLogConfigured")
)

//+kubebuilder:object:root=true
Expand Down
1 change: 0 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,5 @@ func initGardenerClients(kubeconfigPath string, namespace string) (client.Client
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to register Gardener schema")
}

return gardenerClient, shootClient, dynamicKubeconfigAPI, nil
}
21 changes: 0 additions & 21 deletions converter_config.json

This file was deleted.

305 changes: 305 additions & 0 deletions internal/auditlogging/auditlogging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
package auditlogging

import (
"context"
"encoding/json"
"fmt"
"github.com/go-logr/logr"
"os"

gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1"
"github.com/pkg/errors"
v12 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
auditlogSecretReference = "auditlog-credentials"
auditlogExtensionType = "shoot-auditlog-service"
)

//go:generate mockery --name=AuditLogging
type AuditLogging interface {
Enable(ctx context.Context, shoot *gardener.Shoot) (bool, error)
}

//go:generate mockery --name=auditLogConfigurator
type auditLogConfigurator interface {
canEnableAuditLogsForShoot(seedName string) bool
getTenantConfigPath() string
getPolicyConfigMapName() string
getSeedObj(ctx context.Context, seedKey types.NamespacedName) (gardener.Seed, error)
getLogInstance() logr.Logger
updateShoot(ctx context.Context, shoot *gardener.Shoot) error
getConfigFromFile() (data map[string]map[string]AuditLogData, err error)
}

type AuditLog struct {
auditLogConfigurator
}

type auditLogConfig struct {
tenantConfigPath string
policyConfigMapName string
client client.Client
log logr.Logger
}

type AuditLogData struct {
TenantID string `json:"tenantID"`
ServiceURL string `json:"serviceURL"`
SecretName string `json:"secretName"`
}

type AuditlogExtensionConfig struct {
metav1.TypeMeta `json:",inline"`

// Type is the type of auditlog service provider.
Type string `json:"type"`
// TenantID is the id of the tenant.
TenantID string `json:"tenantID"`
// ServiceURL is the URL of the auditlog service.
ServiceURL string `json:"serviceURL"`
// SecretReferenceName is the name of the reference for the secret containing the auditlog service credentials.
SecretReferenceName string `json:"secretReferenceName"`
}

func NewAuditLogging(auditLogTenantConfigPath, auditLogPolicyConfigMapName string, k8s client.Client, log logr.Logger) AuditLogging {
return &AuditLog{
auditLogConfigurator: newAuditLogConfigurator(auditLogTenantConfigPath, auditLogPolicyConfigMapName, k8s, log),
}
}

func newAuditLogConfigurator(auditLogTenantConfigPath, auditLogPolicyConfigMapName string, k8s client.Client, log logr.Logger) auditLogConfigurator {
return &auditLogConfig{
tenantConfigPath: auditLogTenantConfigPath,
policyConfigMapName: auditLogPolicyConfigMapName,
client: k8s,
log: log,
}
}

func (a *auditLogConfig) canEnableAuditLogsForShoot(seedName string) bool {
return seedName != "" && a.tenantConfigPath != ""
}

func (a *auditLogConfig) getTenantConfigPath() string {
return a.tenantConfigPath
}

func (a *auditLogConfig) getPolicyConfigMapName() string {
return a.policyConfigMapName
}

func (a *auditLogConfig) getSeedObj(ctx context.Context, seedKey types.NamespacedName) (gardener.Seed, error) {
var seed gardener.Seed
if err := a.client.Get(ctx, seedKey, &seed); err != nil {
return gardener.Seed{}, err
}
return seed, nil
}

func (al *AuditLog) Enable(ctx context.Context, shoot *gardener.Shoot) (bool, error) {
log := al.getLogInstance()
seedName := getSeedName(*shoot)

if !al.canEnableAuditLogsForShoot(seedName) {
log.Info("Seed name or Tenant config path is empty while configuring Audit Logs on shoot: " + shoot.Name)
Disper marked this conversation as resolved.
Show resolved Hide resolved
return false, nil
}

auditConfigFromFile, err := al.getConfigFromFile()
if err != nil {
return false, errors.Wrap(err, "Cannot get Audit Log config from file")
}

configureAuditPolicy(shoot, al.getPolicyConfigMapName())

seedKey := types.NamespacedName{Name: seedName, Namespace: ""}
seed, err := al.getSeedObj(ctx, seedKey)
if err != nil {
return false, errors.Wrap(err, "Cannot get Gardener Seed object")
}

annotated, err := enableAuditLogs(shoot, auditConfigFromFile, seed.Spec.Provider.Type)

if err != nil {
return false, errors.Wrap(err, "Error during enabling Audit Logs on shoot: "+shoot.Name)
}

if annotated {
if err = al.updateShoot(ctx, shoot); err != nil {
return false, errors.Wrap(err, "Cannot update shoot")
}
}

return true, nil
}

func enableAuditLogs(shoot *gardener.Shoot, auditConfigFromFile map[string]map[string]AuditLogData, providerType string) (bool, error) {
providerConfig := auditConfigFromFile[providerType]
if providerConfig == nil {
return false, fmt.Errorf("cannot find config for provider %s", providerType)
Disper marked this conversation as resolved.
Show resolved Hide resolved
}

auditID := shoot.Spec.Region
if auditID == "" {
return false, errors.New("shoot has no region set")
}

tenant, ok := providerConfig[auditID]
if !ok {
return false, fmt.Errorf("auditlog config for region %s, provider %s is empty", auditID, providerType)
}

changedExt, err := configureExtension(shoot, tenant)
changedSec := configureSecret(shoot, tenant)

return changedExt || changedSec, err
}

func configureExtension(shoot *gardener.Shoot, config AuditLogData) (changed bool, err error) {
changed = false
const (
extensionKind = "AuditlogConfig"
extensionVersion = "service.auditlog.extensions.gardener.cloud/v1alpha1"
extensionType = "standard"
)

ext := findExtension(shoot)
if ext != nil {
cfg := AuditlogExtensionConfig{}
err = json.Unmarshal(ext.ProviderConfig.Raw, &cfg)
if err != nil {
return
}

if cfg.Kind == extensionKind &&
Copy link
Member

Choose a reason for hiding this comment

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

How about extracting this check to some separate method like
isExtensionModified to make this method shorter and possibly make the unit testing easier?

cfg.Type == extensionType &&
cfg.TenantID == config.TenantID &&
cfg.ServiceURL == config.ServiceURL &&
cfg.SecretReferenceName == auditlogSecretReference {
return false, nil
}
} else {
shoot.Spec.Extensions = append(shoot.Spec.Extensions, gardener.Extension{
Type: auditlogExtensionType,
})
ext = &shoot.Spec.Extensions[len(shoot.Spec.Extensions)-1]
}

changed = true

cfg := AuditlogExtensionConfig{
TypeMeta: metav1.TypeMeta{
Kind: extensionKind,
APIVersion: extensionVersion,
},
Type: extensionType,
TenantID: config.TenantID,
ServiceURL: config.ServiceURL,
SecretReferenceName: auditlogSecretReference,
}

ext.ProviderConfig = &runtime.RawExtension{}
ext.ProviderConfig.Raw, err = json.Marshal(cfg)

return
}

func findExtension(shoot *gardener.Shoot) *gardener.Extension {
for i, e := range shoot.Spec.Extensions {
if e.Type == auditlogExtensionType {
return &shoot.Spec.Extensions[i]
}
}

return nil
}

func findSecret(shoot *gardener.Shoot) *gardener.NamedResourceReference {
for i, e := range shoot.Spec.Resources {
if e.Name == auditlogSecretReference {
return &shoot.Spec.Resources[i]
}
}

return nil
}

func configureSecret(shoot *gardener.Shoot, config AuditLogData) (changed bool) {
changed = false

sec := findSecret(shoot)
if sec != nil {
if sec.Name == auditlogSecretReference &&
sec.ResourceRef.APIVersion == "v1" &&
sec.ResourceRef.Kind == "Secret" &&
sec.ResourceRef.Name == config.SecretName {
return
}
} else {
shoot.Spec.Resources = append(shoot.Spec.Resources, gardener.NamedResourceReference{})
sec = &shoot.Spec.Resources[len(shoot.Spec.Resources)-1]
}

changed = true

sec.Name = auditlogSecretReference
sec.ResourceRef.APIVersion = "v1"
sec.ResourceRef.Kind = "Secret"
sec.ResourceRef.Name = config.SecretName

return
}

func (a *auditLogConfig) getConfigFromFile() (data map[string]map[string]AuditLogData, err error) {
file, err := os.Open(a.tenantConfigPath)

if err != nil {
return nil, err
}

defer func(file *os.File) {
_ = file.Close()
}(file)

if err := json.NewDecoder(file).Decode(&data); err != nil {
return nil, err
}
return data, nil
}

func getSeedName(shoot gardener.Shoot) string {
if shoot.Spec.SeedName != nil {
return *shoot.Spec.SeedName
}
return ""
}

func configureAuditPolicy(shoot *gardener.Shoot, policyConfigMapName string) {
if shoot.Spec.Kubernetes.KubeAPIServer == nil {
shoot.Spec.Kubernetes.KubeAPIServer = &gardener.KubeAPIServerConfig{}
}

shoot.Spec.Kubernetes.KubeAPIServer.AuditConfig = newAuditPolicyConfig(policyConfigMapName)
}

func newAuditPolicyConfig(policyConfigMapName string) *gardener.AuditConfig {
return &gardener.AuditConfig{
AuditPolicy: &gardener.AuditPolicy{
ConfigMapRef: &v12.ObjectReference{Name: policyConfigMapName},
},
}
}

func (a *auditLogConfig) updateShoot(ctx context.Context, shoot *gardener.Shoot) error {
return a.client.Update(ctx, shoot)
}

func (a *auditLogConfig) getLogInstance() logr.Logger {
return a.log
}
1 change: 1 addition & 0 deletions internal/auditlogging/auditlogging_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package auditlogging
3 changes: 3 additions & 0 deletions internal/controller/runtime/fsm/runtime_fsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/go-logr/logr"
imv1 "github.com/kyma-project/infrastructure-manager/api/v1"
"github.com/kyma-project/infrastructure-manager/internal/auditlogging"
"github.com/kyma-project/infrastructure-manager/internal/gardener/shoot"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -62,6 +63,7 @@ type fsm struct {
log logr.Logger
K8s
RCCfg
auditlogging.AuditLogging
}

func (m *fsm) Run(ctx context.Context, v imv1.Runtime) (ctrl.Result, error) {
Expand Down Expand Up @@ -105,5 +107,6 @@ func NewFsm(log logr.Logger, cfg RCCfg, k8s K8s) Fsm {
RCCfg: cfg,
log: log,
K8s: k8s,
AuditLogging: auditlogging.NewAuditLogging(cfg.AuditLog.TenantConfigPath, cfg.AuditLog.PolicyConfigMapName, k8s.ShootClient, log),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,7 @@ func sFnApplyClusterRoleBindings(ctx context.Context, m *fsm, s *systemState) (s
}
}

s.instance.UpdateStateReady(
imv1.ConditionTypeRuntimeConfigured,
imv1.ConditionReasonConfigurationCompleted,
"kubeconfig admin access updated",
)
return updateStatusAndStop()
return switchState(sFnConfigureAuditLog)
}

//nolint:gochecknoglobals
Expand Down
Loading
Loading