From de046c65b037e668523bb2502ba10d5952b196d2 Mon Sep 17 00:00:00 2001 From: RoyUP9 <87927115+RoyUP9@users.noreply.github.com> Date: Tue, 28 Dec 2021 15:44:37 +0200 Subject: [PATCH] Mizu install mode (#566) --- README.md | 6 +- acceptanceTests/tap_test.go | 248 ------------------ acceptanceTests/testsUtils.go | 15 -- agent/main.go | 33 +-- agent/pkg/config/config.go | 8 - agent/pkg/controllers/status_controller.go | 9 - agent/pkg/providers/status_provider.go | 2 +- cli/cmd/install.go | 21 ++ cli/cmd/installRunner.go | 71 +++++ cli/cmd/tap.go | 1 - cli/cmd/tapRunner.go | 91 ++----- cli/config/configStructs/tapConfig.go | 9 - cli/mizu/consts.go | 14 +- cli/resources/createResources.go | 83 ++++-- docs/CONFIGURATION.md | 2 - .../{DAEMON_MODE.md => INSTALL_STANDALONE.md} | 16 +- docs/PERMISSIONS.md | 6 +- shared/models.go | 21 +- 18 files changed, 207 insertions(+), 449 deletions(-) create mode 100644 cli/cmd/install.go create mode 100644 cli/cmd/installRunner.go rename docs/{DAEMON_MODE.md => INSTALL_STANDALONE.md} (79%) diff --git a/README.md b/README.md index 56d537899..625ee0887 100644 --- a/README.md +++ b/README.md @@ -179,9 +179,9 @@ tap proxy-host: 0.0.0.0 and when changed it will support accessing by IP -### Run in daemon mode +### Install Mizu standalone -Mizu can be run detached from the cli using the daemon flag: `mizu tap --daemon`. This type of mizu instance will run +Mizu can be run detached from the cli using the install command: `mizu install`. This type of mizu instance will run indefinitely in the cluster. -For more information please refer to [DAEMON MODE](docs/DAEMON_MODE.md) +For more information please refer to [INSTALL STANDALONE](docs/INSTALL_STANDALONE.md) diff --git a/acceptanceTests/tap_test.go b/acceptanceTests/tap_test.go index 51b57a4ea..965b77c87 100644 --- a/acceptanceTests/tap_test.go +++ b/acceptanceTests/tap_test.go @@ -880,251 +880,3 @@ func TestTapDumpLogs(t *testing.T) { return } } - -func TestDaemonSeeTraffic(t *testing.T) { - if testing.Short() { - t.Skip("ignored acceptance test") - } - - tests := []int{50} - - for _, entriesCount := range tests { - t.Run(fmt.Sprintf("%d", entriesCount), func(t *testing.T) { - cliPath, cliPathErr := getCliPath() - if cliPathErr != nil { - t.Errorf("failed to get cli path, err: %v", cliPathErr) - return - } - - tapDaemonCmdArgs := getDefaultTapCommandArgsWithDaemonMode() - - tapNamespace := getDefaultTapNamespace() - tapDaemonCmdArgs = append(tapDaemonCmdArgs, tapNamespace...) - - tapCmd := exec.Command(cliPath, tapDaemonCmdArgs...) - - viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...) - - t.Cleanup(func() { - daemonCleanup(t, viewCmd) - }) - - t.Logf("running command: %v", tapCmd.String()) - if err := tapCmd.Run(); err != nil { - t.Errorf("error occured while running the tap command, err: %v", err) - return - } - - t.Logf("running command: %v", viewCmd.String()) - if err := viewCmd.Start(); err != nil { - t.Errorf("error occured while running the view command, err: %v", err) - return - } - - apiServerUrl := getApiServerUrl(defaultApiServerPort) - if err := waitTapPodsReady(apiServerUrl); err != nil { - t.Errorf("failed to start tap pods on time, err: %v", err) - return - } - - proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName) - for i := 0; i < entriesCount; i++ { - if _, requestErr := executeHttpGetRequest(fmt.Sprintf("%v/get", proxyUrl)); requestErr != nil { - t.Errorf("failed to send proxy request, err: %v", requestErr) - return - } - } - - entriesCheckFunc := func() error { - timestamp := time.Now().UnixNano() / int64(time.Millisecond) - - entries, err := getDBEntries(timestamp, entriesCount, 1*time.Second) - if err != nil { - return err - } - err = checkEntriesAtLeast(entries, 1) - if err != nil { - return err - } - entry := entries[0] - - entryUrl := fmt.Sprintf("%v/entries/%v", apiServerUrl, entry["id"]) - requestResult, requestErr := executeHttpGetRequest(entryUrl) - if requestErr != nil { - return fmt.Errorf("failed to get entry, err: %v", requestErr) - } - - if requestResult == nil { - return fmt.Errorf("unexpected nil entry result") - } - - return nil - } - if err := retriesExecute(shortRetriesCount, entriesCheckFunc); err != nil { - t.Errorf("%v", err) - return - } - }) - } -} - -func TestDaemonMultipleNamespacesSeePods(t *testing.T) { - if testing.Short() { - t.Skip("ignored acceptance test") - } - - expectedPods := []PodDescriptor{ - {Name: "httpbin", Namespace: "mizu-tests"}, - {Name: "httpbin2", Namespace: "mizu-tests"}, - {Name: "httpbin", Namespace: "mizu-tests2"}, - } - - cliPath, cliPathErr := getCliPath() - if cliPathErr != nil { - t.Errorf("failed to get cli path, err: %v", cliPathErr) - return - } - - tapCmdArgs := getDefaultTapCommandArgsWithDaemonMode() - var namespacesCmd []string - for _, expectedPod := range expectedPods { - namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace) - } - tapCmdArgs = append(tapCmdArgs, namespacesCmd...) - - tapCmd := exec.Command(cliPath, tapCmdArgs...) - - viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...) - - t.Cleanup(func() { - daemonCleanup(t, viewCmd) - }) - - t.Logf("running command: %v", tapCmd.String()) - if err := tapCmd.Run(); err != nil { - t.Errorf("failed to start tap command, err: %v", err) - return - } - - t.Logf("running command: %v", viewCmd.String()) - if err := viewCmd.Start(); err != nil { - t.Errorf("error occured while running the view command, err: %v", err) - return - } - - apiServerUrl := getApiServerUrl(defaultApiServerPort) - if err := waitTapPodsReady(apiServerUrl); err != nil { - t.Errorf("failed to start tap pods on time, err: %v", err) - return - } - - podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl) - requestResult, requestErr := executeHttpGetRequest(podsUrl) - if requestErr != nil { - t.Errorf("failed to get tap status, err: %v", requestErr) - return - } - - pods, err := getPods(requestResult) - if err != nil { - t.Errorf("failed to get pods, err: %v", err) - return - } - - if len(expectedPods) != len(pods) { - t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods)) - return - } - - for _, expectedPod := range expectedPods { - if !isPodDescriptorInPodArray(pods, expectedPod) { - t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name) - return - } - } -} - -func TestDaemonSingleNamespaceSeePods(t *testing.T) { - if testing.Short() { - t.Skip("ignored acceptance test") - } - - expectedPods := []PodDescriptor{ - {Name: "httpbin", Namespace: "mizu-tests"}, - {Name: "httpbin2", Namespace: "mizu-tests"}, - } - unexpectedPods := []PodDescriptor{ - {Name: "httpbin", Namespace: "mizu-tests2"}, - } - - cliPath, cliPathErr := getCliPath() - if cliPathErr != nil { - t.Errorf("failed to get cli path, err: %v", cliPathErr) - return - } - - tapCmdArgs := getDefaultTapCommandArgsWithDaemonMode() - var namespacesCmd []string - for _, expectedPod := range expectedPods { - namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace) - } - tapCmdArgs = append(tapCmdArgs, namespacesCmd...) - - tapCmd := exec.Command(cliPath, tapCmdArgs...) - - viewCmd := exec.Command(cliPath, getDefaultViewCommandArgs()...) - - t.Cleanup(func() { - daemonCleanup(t, viewCmd) - }) - - t.Logf("running command: %v", tapCmd.String()) - if err := tapCmd.Run(); err != nil { - t.Errorf("failed to start tap command, err: %v", err) - return - } - - t.Logf("running command: %v", viewCmd.String()) - if err := viewCmd.Start(); err != nil { - t.Errorf("error occured while running the view command, err: %v", err) - return - } - - apiServerUrl := getApiServerUrl(defaultApiServerPort) - if err := waitTapPodsReady(apiServerUrl); err != nil { - t.Errorf("failed to start tap pods on time, err: %v", err) - return - } - - podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl) - requestResult, requestErr := executeHttpGetRequest(podsUrl) - if requestErr != nil { - t.Errorf("failed to get tap status, err: %v", requestErr) - return - } - - pods, err := getPods(requestResult) - if err != nil { - t.Errorf("failed to get pods, err: %v", err) - return - } - - for _, unexpectedPod := range unexpectedPods { - if isPodDescriptorInPodArray(pods, unexpectedPod) { - t.Errorf("unexpected result - unexpected pod found, pod namespace: %v, pod name: %v", unexpectedPod.Namespace, unexpectedPod.Name) - return - } - } - - if len(expectedPods) != len(pods) { - t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods)) - return - } - - for _, expectedPod := range expectedPods { - if !isPodDescriptorInPodArray(pods, expectedPod) { - t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name) - return - } - } -} diff --git a/acceptanceTests/testsUtils.go b/acceptanceTests/testsUtils.go index 715df51d7..e1aee5806 100644 --- a/acceptanceTests/testsUtils.go +++ b/acceptanceTests/testsUtils.go @@ -13,7 +13,6 @@ import ( "strings" "sync" "syscall" - "testing" "time" "github.com/gorilla/websocket" @@ -105,10 +104,6 @@ func getDefaultTapCommandArgs() []string { return append([]string{tapCommand}, defaultCmdArgs...) } -func getDefaultTapCommandArgsWithDaemonMode() []string { - return append(getDefaultTapCommandArgs(), "--daemon") -} - func getDefaultTapCommandArgsWithRegex(regex string) []string { tapCommand := "tap" defaultCmdArgs := getDefaultCommandArgs() @@ -324,16 +319,6 @@ func getLogsPath() (string, error) { return logsPath, nil } -func daemonCleanup(t *testing.T, viewCmd *exec.Cmd) { - if err := runMizuClean(); err != nil { - t.Logf("error running mizu clean: %v", err) - } - - if err := cleanupCommand(viewCmd); err != nil { - t.Logf("failed to cleanup view command, err: %v", err) - } -} - // waitTimeout waits for the waitgroup for the specified max timeout. // Returns true if waiting timed out. func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { diff --git a/agent/main.go b/agent/main.go index 9ea0369ac..38c7e569d 100644 --- a/agent/main.go +++ b/agent/main.go @@ -22,6 +22,7 @@ import ( "path" "path/filepath" "plugin" + "regexp" "sort" "syscall" "time" @@ -131,10 +132,6 @@ func main() { } } - if config.Config.SyncTappers { - startSyncingTappers() - } - hostApi(outputItemsChannel) } else if *harsReaderMode { outputItemsChannel := make(chan *tapApi.OutputChannelItem, 1000) @@ -451,33 +448,19 @@ func handleIncomingMessageAsTapper(socketConnection *websocket.Conn) { } } -func startSyncingTappers() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - kubernetesProvider, err := kubernetes.NewProviderInCluster() - if err != nil { - logger.Log.Fatalf("error creating k8s provider: %+v", err) - } - - if _, err := startMizuTapperSyncer(ctx, kubernetesProvider); err != nil { - logger.Log.Fatalf("error initializing tapper syncer: %+v", err) - } -} - -func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider) (*kubernetes.MizuTapperSyncer, error) { +func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, targetNamespaces []string, podFilterRegex regexp.Regexp, ignoredUserAgents []string, mizuApiFilteringOptions tapApi.TrafficFilteringOptions, istio bool) (*kubernetes.MizuTapperSyncer, error) { tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{ - TargetNamespaces: config.Config.TargetNamespaces, - PodFilterRegex: config.Config.TapTargetRegex.Regexp, + TargetNamespaces: targetNamespaces, + PodFilterRegex: podFilterRegex, MizuResourcesNamespace: config.Config.MizuResourcesNamespace, AgentImage: config.Config.AgentImage, TapperResources: config.Config.TapperResources, ImagePullPolicy: v1.PullPolicy(config.Config.PullPolicy), LogLevel: config.Config.LogLevel, - IgnoredUserAgents: config.Config.IgnoredUserAgents, - MizuApiFilteringOptions: config.Config.MizuApiFilteringOptions, - MizuServiceAccountExists: true, //assume service account exists since daemon mode will not function without it anyway - Istio: config.Config.Istio, + IgnoredUserAgents: ignoredUserAgents, + MizuApiFilteringOptions: mizuApiFilteringOptions, + MizuServiceAccountExists: true, //assume service account exists since install mode will not function without it anyway + Istio: istio, }, time.Now()) if err != nil { diff --git a/agent/pkg/config/config.go b/agent/pkg/config/config.go index a25ead8aa..6a38e61e9 100644 --- a/agent/pkg/config/config.go +++ b/agent/pkg/config/config.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "github.com/up9inc/mizu/shared" - "github.com/up9inc/mizu/tap/api" "io/ioutil" "os" ) @@ -12,7 +11,6 @@ import ( // these values are used when the config.json file is not present const ( defaultMaxDatabaseSizeBytes int64 = 200 * 1000 * 1000 - defaultRegexTarget string = ".*" DefaultDatabasePath string = "./entries" ) @@ -48,14 +46,8 @@ func applyDefaultConfig() error { } func getDefaultConfig() (*shared.MizuAgentConfig, error) { - regex, err := api.CompileRegexToSerializableRegexp(defaultRegexTarget) - if err != nil { - return nil, err - } return &shared.MizuAgentConfig{ - TapTargetRegex: *regex, MaxDBSizeBytes: defaultMaxDatabaseSizeBytes, AgentDatabasePath: DefaultDatabasePath, - SyncTappers: false, }, nil } diff --git a/agent/pkg/controllers/status_controller.go b/agent/pkg/controllers/status_controller.go index 512cb4ddb..8819238db 100644 --- a/agent/pkg/controllers/status_controller.go +++ b/agent/pkg/controllers/status_controller.go @@ -2,12 +2,10 @@ package controllers import ( "encoding/json" - "fmt" "github.com/gin-gonic/gin" "github.com/up9inc/mizu/shared" "github.com/up9inc/mizu/shared/logger" "mizuserver/pkg/api" - "mizuserver/pkg/config" "mizuserver/pkg/holder" "mizuserver/pkg/providers" "mizuserver/pkg/up9" @@ -17,13 +15,6 @@ import ( ) func HealthCheck(c *gin.Context) { - if config.Config.SyncTappers { - if providers.ExpectedTapperAmount != providers.TappersCount { - c.JSON(http.StatusInternalServerError, fmt.Sprintf("expecting more tappers than are actually connected (%d expected, %d connected)", providers.ExpectedTapperAmount, providers.TappersCount)) - return - } - } - tappers := make([]shared.TapperStatus, 0) for _, value := range providers.TappersStatus { tappers = append(tappers, value) diff --git a/agent/pkg/providers/status_provider.go b/agent/pkg/providers/status_provider.go index 85fe8793b..9ed1a2a46 100644 --- a/agent/pkg/providers/status_provider.go +++ b/agent/pkg/providers/status_provider.go @@ -20,7 +20,7 @@ var ( TappersStatus map[string]shared.TapperStatus authStatus *models.AuthStatus RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime) - ExpectedTapperAmount = -1 //only relevant in daemon mode as cli manages tappers otherwise + ExpectedTapperAmount = -1 //only relevant in install mode as cli manages tappers otherwise tappersCountLock = sync.Mutex{} ) diff --git a/cli/cmd/install.go b/cli/cmd/install.go new file mode 100644 index 000000000..dfca9bf1a --- /dev/null +++ b/cli/cmd/install.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/up9inc/mizu/cli/telemetry" +) + +var installCmd = &cobra.Command{ + Use: "install", + Short: "Installs mizu components", + RunE: func(cmd *cobra.Command, args []string) error { + go telemetry.ReportRun("install", nil) + runMizuInstall() + return nil + }, +} + +func init() { + rootCmd.AddCommand(installCmd) +} + diff --git a/cli/cmd/installRunner.go b/cli/cmd/installRunner.go new file mode 100644 index 000000000..e73b26be6 --- /dev/null +++ b/cli/cmd/installRunner.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "context" + "errors" + "fmt" + "github.com/creasty/defaults" + "github.com/up9inc/mizu/cli/config" + "github.com/up9inc/mizu/cli/errormessage" + "github.com/up9inc/mizu/cli/resources" + "github.com/up9inc/mizu/cli/uiUtils" + "github.com/up9inc/mizu/shared" + "github.com/up9inc/mizu/shared/logger" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func runMizuInstall() { + kubernetesProvider, err := getKubernetesProviderForCli() + if err != nil { + return + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() // cancel will be called when this function exits + + var serializedValidationRules string + var serializedContract string + + var defaultMaxEntriesDBSizeBytes int64 = 200 * 1000 * 1000 + + defaultResources := shared.Resources{} + defaults.Set(&defaultResources) + + mizuAgentConfig := getInstallMizuAgentConfig(defaultMaxEntriesDBSizeBytes, defaultResources) + serializedMizuConfig, err := getSerializedMizuAgentConfig(mizuAgentConfig) + if err != nil { + logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error serializing mizu config: %v", errormessage.FormatError(err))) + return + } + + if err = resources.CreateInstallMizuResources(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.AgentImage, nil, defaultMaxEntriesDBSizeBytes, defaultResources, config.Config.ImagePullPolicy(), config.Config.LogLevel(), false); err != nil { + var statusError *k8serrors.StatusError + if errors.As(err, &statusError) { + if statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists { + logger.Log.Info("Mizu is already running in this namespace, change the `mizu-resources-namespace` configuration or run `mizu clean` to remove the currently running Mizu instance") + } + } else { + defer resources.CleanUpMizuResources(ctx, cancel, kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace) + logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err))) + } + + return + } + + logger.Log.Infof(uiUtils.Magenta, "Created Mizu Agent components, run `mizu view` to connect to the mizu daemon instance") +} + +func getInstallMizuAgentConfig(maxDBSizeBytes int64, tapperResources shared.Resources) *shared.MizuAgentConfig { + mizuAgentConfig := shared.MizuAgentConfig{ + MaxDBSizeBytes: maxDBSizeBytes, + AgentImage: config.Config.AgentImage, + PullPolicy: config.Config.ImagePullPolicyStr, + LogLevel: config.Config.LogLevel(), + TapperResources: tapperResources, + MizuResourcesNamespace: config.Config.MizuResourcesNamespace, + AgentDatabasePath: shared.DataDirPath, + } + + return &mizuAgentConfig +} diff --git a/cli/cmd/tap.go b/cli/cmd/tap.go index f93a8f65a..ebaa2befd 100644 --- a/cli/cmd/tap.go +++ b/cli/cmd/tap.go @@ -119,6 +119,5 @@ func init() { tapCmd.Flags().StringP(configStructs.WorkspaceTapName, "w", defaultTapConfig.Workspace, "Uploads traffic to your UP9 workspace for further analysis (requires auth)") tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules") tapCmd.Flags().String(configStructs.ContractFile, defaultTapConfig.ContractFile, "OAS/Swagger file to validate to monitor the contracts") - tapCmd.Flags().Bool(configStructs.DaemonModeTapName, defaultTapConfig.DaemonMode, "Run mizu in daemon mode, detached from the cli") tapCmd.Flags().Bool(configStructs.IstioName, defaultTapConfig.Istio, "Record decrypted traffic if the cluster configured with istio and mtls") } diff --git a/cli/cmd/tapRunner.go b/cli/cmd/tapRunner.go index 9b53f7f17..44ac2532f 100644 --- a/cli/cmd/tapRunner.go +++ b/cli/cmd/tapRunner.go @@ -44,13 +44,9 @@ var apiProvider *apiserver.Provider func RunMizuTap() { state.startTime = time.Now() - mizuApiFilteringOptions, err := getMizuApiFilteringOptions() apiProvider = apiserver.NewProvider(GetApiServerUrl(), apiserver.DefaultRetries, apiserver.DefaultTimeout) - if err != nil { - logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error parsing regex-masking: %v", errormessage.FormatError(err))) - return - } + var err error var serializedValidationRules string if config.Config.Tap.EnforcePolicyFile != "" { serializedValidationRules, err = readValidationRules(config.Config.Tap.EnforcePolicyFile) @@ -94,12 +90,7 @@ func RunMizuTap() { state.targetNamespaces = getNamespaces(kubernetesProvider) - mizuAgentConfig, err := getMizuAgentConfig(state.targetNamespaces, mizuApiFilteringOptions) - if err != nil { - logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error getting mizu config: %v", errormessage.FormatError(err))) - return - } - + mizuAgentConfig := getTapMizuAgentConfig() serializedMizuConfig, err := getSerializedMizuAgentConfig(mizuAgentConfig) if err != nil { logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error serializing mizu config: %v", errormessage.FormatError(err))) @@ -132,68 +123,41 @@ func RunMizuTap() { } logger.Log.Infof("Waiting for Mizu Agent to start...") - if state.mizuServiceAccountExists, err = resources.CreateMizuResources(ctx, cancel, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.Tap.DaemonMode, config.Config.AgentImage, getSyncEntriesConfig(), config.Config.Tap.MaxEntriesDBSizeBytes(), config.Config.Tap.ApiServerResources, config.Config.ImagePullPolicy(), config.Config.LogLevel(), config.Config.Tap.NoPersistentVolumeClaim); err != nil { - logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err))) - + if state.mizuServiceAccountExists, err = resources.CreateTapMizuResources(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.AgentImage, getSyncEntriesConfig(), config.Config.Tap.MaxEntriesDBSizeBytes(), config.Config.Tap.ApiServerResources, config.Config.ImagePullPolicy(), config.Config.LogLevel()); err != nil { var statusError *k8serrors.StatusError if errors.As(err, &statusError) { if statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists { logger.Log.Info("Mizu is already running in this namespace, change the `mizu-resources-namespace` configuration or run `mizu clean` to remove the currently running Mizu instance") } + } else { + defer resources.CleanUpMizuResources(ctx, cancel, kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace) + logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err))) } + return } - if config.Config.Tap.DaemonMode { - if err := handleDaemonModePostCreation(cancel, kubernetesProvider); err != nil { - defer finishMizuExecution(kubernetesProvider, apiProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace) - cancel() - } else { - logger.Log.Infof(uiUtils.Magenta, "Mizu is now running in daemon mode, run `mizu view` to connect to the mizu daemon instance") - } - } else { - defer finishMizuExecution(kubernetesProvider, apiProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace) - go goUtils.HandleExcWrapper(watchApiServerEvents, ctx, kubernetesProvider, cancel) - go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel) + defer finishMizuExecution(kubernetesProvider, apiProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace) - // block until exit signal or error - utils.WaitForFinish(ctx, cancel) - } + go goUtils.HandleExcWrapper(watchApiServerEvents, ctx, kubernetesProvider, cancel) + go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel) + + // block until exit signal or error + utils.WaitForFinish(ctx, cancel) } -func handleDaemonModePostCreation(cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) error { - apiProvider := apiserver.NewProvider(GetApiServerUrl(), 90, 1*time.Second) - - if err := waitForDaemonModeToBeReady(cancel, kubernetesProvider, apiProvider); err != nil { - return err - } - - return nil -} - -func getMizuAgentConfig(targetNamespaces []string, mizuApiFilteringOptions *api.TrafficFilteringOptions) (*shared.MizuAgentConfig, error) { - serializableRegex, err := api.CompileRegexToSerializableRegexp(config.Config.Tap.PodRegexStr) - if err != nil { - return nil, err - } - +func getTapMizuAgentConfig() *shared.MizuAgentConfig { mizuAgentConfig := shared.MizuAgentConfig{ - TapTargetRegex: *serializableRegex, - MaxDBSizeBytes: config.Config.Tap.MaxEntriesDBSizeBytes(), - TargetNamespaces: targetNamespaces, - AgentImage: config.Config.AgentImage, - PullPolicy: config.Config.ImagePullPolicyStr, - LogLevel: config.Config.LogLevel(), - IgnoredUserAgents: config.Config.Tap.IgnoredUserAgents, - TapperResources: config.Config.Tap.TapperResources, - MizuResourcesNamespace: config.Config.MizuResourcesNamespace, - MizuApiFilteringOptions: *mizuApiFilteringOptions, - AgentDatabasePath: shared.DataDirPath, - Istio: config.Config.Tap.Istio, - SyncTappers: config.Config.Tap.DaemonMode, + MaxDBSizeBytes: config.Config.Tap.MaxEntriesDBSizeBytes(), + AgentImage: config.Config.AgentImage, + PullPolicy: config.Config.ImagePullPolicyStr, + LogLevel: config.Config.LogLevel(), + TapperResources: config.Config.Tap.TapperResources, + MizuResourcesNamespace: config.Config.MizuResourcesNamespace, + AgentDatabasePath: shared.DataDirPath, } - return &mizuAgentConfig, nil + return &mizuAgentConfig } /* @@ -215,17 +179,6 @@ func printTappedPodsPreview(ctx context.Context, kubernetesProvider *kubernetes. } } -func waitForDaemonModeToBeReady(cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, apiProvider *apiserver.Provider) error { - go startProxyReportErrorIfAny(kubernetesProvider, cancel) - - // TODO: TRA-3903 add a smarter test to see that tapping/pod watching is functioning properly - if err := apiProvider.TestConnection(); err != nil { - logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Mizu was not ready in time, for more info check logs at %s", fsUtils.GetLogFilePath())) - return err - } - return nil -} - func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider *kubernetes.Provider, targetNamespaces []string, mizuApiFilteringOptions api.TrafficFilteringOptions, startTime time.Time) error { tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{ TargetNamespaces: targetNamespaces, diff --git a/cli/config/configStructs/tapConfig.go b/cli/config/configStructs/tapConfig.go index 43c29523d..bd90ca262 100644 --- a/cli/config/configStructs/tapConfig.go +++ b/cli/config/configStructs/tapConfig.go @@ -3,8 +3,6 @@ package configStructs import ( "errors" "fmt" - "github.com/up9inc/mizu/cli/uiUtils" - "github.com/up9inc/mizu/shared/logger" "regexp" "github.com/up9inc/mizu/shared" @@ -24,7 +22,6 @@ const ( WorkspaceTapName = "workspace" EnforcePolicyFile = "traffic-validation-file" ContractFile = "contract" - DaemonModeTapName = "daemon" IstioName = "istio" ) @@ -47,8 +44,6 @@ type TapConfig struct { AskUploadConfirmation bool `yaml:"ask-upload-confirmation" default:"true"` ApiServerResources shared.Resources `yaml:"api-server-resources"` TapperResources shared.Resources `yaml:"tapper-resources"` - DaemonMode bool `yaml:"daemon" default:"false"` - NoPersistentVolumeClaim bool `yaml:"no-persistent-volume-claim" default:"false"` Istio bool `yaml:"istio" default:"false"` } @@ -84,9 +79,5 @@ func (config *TapConfig) Validate() error { return errors.New(fmt.Sprintf("Can't run with both --%s and --%s flags", AnalysisTapName, WorkspaceTapName)) } - if config.NoPersistentVolumeClaim && !config.DaemonMode { - logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("the --set tap.no-persistent-volume-claim=true flag has no effect without the --%s flag, the claim will not be created anyway.", DaemonModeTapName)) - } - return nil } diff --git a/cli/mizu/consts.go b/cli/mizu/consts.go index 73621a2ff..cf67a5ab4 100644 --- a/cli/mizu/consts.go +++ b/cli/mizu/consts.go @@ -6,13 +6,13 @@ import ( ) var ( - SemVer = "0.0.1" - Branch = "develop" - GitCommitHash = "" // this var is overridden using ldflags in makefile when building - BuildTimestamp = "" // this var is overridden using ldflags in makefile when building - RBACVersion = "v1" - Platform = "" - DaemonModePersistentVolumeSizeBufferBytes = int64(500 * 1000 * 1000) //500mb + SemVer = "0.0.1" + Branch = "develop" + GitCommitHash = "" // this var is overridden using ldflags in makefile when building + BuildTimestamp = "" // this var is overridden using ldflags in makefile when building + RBACVersion = "v1" + Platform = "" + InstallModePersistentVolumeSizeBufferBytes = int64(500 * 1000 * 1000) //500mb ) const DEVENVVAR = "MIZU_DISABLE_TELEMTRY" diff --git a/cli/resources/createResources.go b/cli/resources/createResources.go index 74d3186c2..80af4a513 100644 --- a/cli/resources/createResources.go +++ b/cli/resources/createResources.go @@ -14,7 +14,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) -func CreateMizuResources(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, isInstallMode bool, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level, noPersistentVolumeClaim bool) (bool, error) { +func CreateTapMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level) (bool, error) { if !isNsRestrictedMode { if err := createMizuNamespace(ctx, kubernetesProvider, mizuResourcesNamespace); err != nil { return false, err @@ -25,11 +25,9 @@ func CreateMizuResources(ctx context.Context, cancel context.CancelFunc, kuberne logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v", errormessage.FormatError(err))) } - mizuServiceAccountExists, err := createRBACIfNecessary(ctx, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace, isInstallMode) + mizuServiceAccountExists, err := createRBACIfNecessary(ctx, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace) if err != nil { - if !isInstallMode { - logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to ensure the resources required for IP resolving. Mizu will not resolve target IPs to names. error: %v", errormessage.FormatError(err))) - } + logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to ensure the resources required for IP resolving. Mizu will not resolve target IPs to names. error: %v", errormessage.FormatError(err))) } var serviceAccountName string @@ -52,29 +50,70 @@ func CreateMizuResources(ctx context.Context, cancel context.CancelFunc, kuberne LogLevel: logLevel, } - if isInstallMode { - if !mizuServiceAccountExists { - defer CleanUpMizuResources(ctx, cancel, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace) - logger.Log.Fatalf(uiUtils.Red, fmt.Sprintf("Failed to ensure the resources required for mizu to run in daemon mode. cannot proceed. error: %v", errormessage.FormatError(err))) - } - if err := createMizuApiServerDeployment(ctx, kubernetesProvider, opts, noPersistentVolumeClaim); err != nil { - return mizuServiceAccountExists, err - } - } else { - if err := createMizuApiServerPod(ctx, kubernetesProvider, opts); err != nil { - return mizuServiceAccountExists, err - } + if err := createMizuApiServerPod(ctx, kubernetesProvider, opts); err != nil { + return mizuServiceAccountExists, err } _, err = kubernetesProvider.CreateService(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName) if err != nil { return mizuServiceAccountExists, err } + logger.Log.Debugf("Successfully created service: %s", kubernetes.ApiServerPodName) return mizuServiceAccountExists, nil } +func CreateInstallMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level, noPersistentVolumeClaim bool) error { + if !isNsRestrictedMode { + if err := createMizuNamespace(ctx, kubernetesProvider, mizuResourcesNamespace); err != nil { + return err + } + logger.Log.Infof("Created mizu namespace") + } + + if err := createMizuConfigmap(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, mizuResourcesNamespace); err != nil { + return err + } + logger.Log.Infof("Created config map") + + _, err := createRBACIfNecessary(ctx, kubernetesProvider, isNsRestrictedMode, mizuResourcesNamespace) + if err != nil { + return err + } + if err := kubernetesProvider.CreateDaemonsetRBAC(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.DaemonRoleName, kubernetes.DaemonRoleBindingName, mizu.RBACVersion); err != nil { + return err + } + logger.Log.Infof("Created RBAC") + + serviceAccountName := kubernetes.ServiceAccountName + opts := &kubernetes.ApiServerOptions{ + Namespace: mizuResourcesNamespace, + PodName: kubernetes.ApiServerPodName, + PodImage: agentImage, + ServiceAccountName: serviceAccountName, + IsNamespaceRestricted: isNsRestrictedMode, + SyncEntriesConfig: syncEntriesConfig, + MaxEntriesDBSizeBytes: maxEntriesDBSizeBytes, + Resources: apiServerResources, + ImagePullPolicy: imagePullPolicy, + LogLevel: logLevel, + } + + if err := createMizuApiServerDeployment(ctx, kubernetesProvider, opts, noPersistentVolumeClaim); err != nil { + return err + } + logger.Log.Infof("Created Api Server deployment") + + _, err = kubernetesProvider.CreateService(ctx, mizuResourcesNamespace, kubernetes.ApiServerPodName, kubernetes.ApiServerPodName) + if err != nil { + return err + } + logger.Log.Infof("Created Api Server service") + + return nil +} + func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuResourcesNamespace string) error { _, err := kubernetesProvider.CreateNamespace(ctx, mizuResourcesNamespace) return err @@ -85,7 +124,7 @@ func createMizuConfigmap(ctx context.Context, kubernetesProvider *kubernetes.Pro return err } -func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider, isNsRestrictedMode bool, mizuResourcesNamespace string, isInstallMode bool) (bool, error) { +func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider, isNsRestrictedMode bool, mizuResourcesNamespace string) (bool, error) { if !isNsRestrictedMode { if err := kubernetesProvider.CreateMizuRBAC(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.ClusterRoleName, kubernetes.ClusterRoleBindingName, mizu.RBACVersion); err != nil { return false, err @@ -95,11 +134,7 @@ func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.P return false, err } } - if isInstallMode { - if err := kubernetesProvider.CreateDaemonsetRBAC(ctx, mizuResourcesNamespace, kubernetes.ServiceAccountName, kubernetes.DaemonRoleName, kubernetes.DaemonRoleBindingName, mizu.RBACVersion); err != nil { - return false, err - } - } + return true, nil } @@ -141,7 +176,7 @@ func tryToCreatePersistentVolumeClaim(ctx context.Context, kubernetesProvider *k return false } - if _, err = kubernetesProvider.CreatePersistentVolumeClaim(ctx, opts.Namespace, kubernetes.PersistentVolumeClaimName, opts.MaxEntriesDBSizeBytes + mizu.DaemonModePersistentVolumeSizeBufferBytes); err != nil { + if _, err = kubernetesProvider.CreatePersistentVolumeClaim(ctx, opts.Namespace, kubernetes.PersistentVolumeClaimName, opts.MaxEntriesDBSizeBytes + mizu.InstallModePersistentVolumeSizeBufferBytes); err != nil { logger.Log.Warningf(uiUtils.Yellow, "An error has occured while creating a persistent volume claim for mizu, this means mizu data will be lost on mizu-api-server pod restart") logger.Log.Debugf("error creating persistent volume claim: %v", err) return false diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index ad9c64889..50609f4ab 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -38,8 +38,6 @@ Please make sure to use full option name (`tap.dry-run` as opposed to `dry-run` * `all-namespaces` - special flag indicating whether Mizu should search and tap pods, matching the regex, in all namespaces. Default is `false`. Please use with caution, tapping too many pods can affect resource consumption. -* `daemon` - instructs Mizu whether to run daemon mode (where CLI command exits after launch, and tapper & api-server pods in Kubernetes continue to run without controlling CLI). Typically supplied as command-line option `--daemon`. Default valie is `false` - * `dry-run` - if true, Mizu will print list of pods matching the supplied (or default) regex and exit without actually tapping the traffic. Default value is `false`. Typically supplied as command-line option `--dry-run` * `proxy-host` - IP address on which proxy to Mizu API service is launched; should be accessible at `proxy-host:gui-port`. Default value is `127.0.0.1` diff --git a/docs/DAEMON_MODE.md b/docs/INSTALL_STANDALONE.md similarity index 79% rename from docs/DAEMON_MODE.md rename to docs/INSTALL_STANDALONE.md index ab28d7d12..28b5b8620 100644 --- a/docs/DAEMON_MODE.md +++ b/docs/INSTALL_STANDALONE.md @@ -1,22 +1,16 @@ -# Mizu daemon mode +# Mizu install standalone -Mizu can be run detached from the cli using the daemon flag: `mizu tap --daemon`. This type of mizu instance will run +Mizu can be run detached from the cli using the install command: `mizu install`. This type of mizu instance will run indefinitely in the cluster. -Please note that daemon mode requires you to have RBAC creation permissions, see the [permissions](PERMISSIONS.md) +Please note that install standalone requires you to have RBAC creation permissions, see the [permissions](PERMISSIONS.md) doc for more details. ```bash -$ mizu tap "^ca.*" --daemon - Mizu will store up to 200MB of traffic, old traffic will be cleared once the limit is reached. - Tapping pods in namespaces "sock-shop" - Waiting for mizu to be ready... (may take a few minutes) - +carts-66c77f5fbb-fq65r - +catalogue-5f4cb7cf5-7zrmn - .. +$ mizu install ``` -## Stop mizu daemon +## Stop mizu install To stop the detached mizu instance and clean all cluster side resources, run `mizu clean` diff --git a/docs/PERMISSIONS.md b/docs/PERMISSIONS.md index 12eb69b8e..b200b7da6 100644 --- a/docs/PERMISSIONS.md +++ b/docs/PERMISSIONS.md @@ -57,11 +57,11 @@ Mizu needs following permissions on your Kubernetes cluster to run properly - get ``` -## Permissions required running with --daemon flag or (optional) for service / pod name resolving +## Permissions required running with install command or (optional) for service / pod name resolving -Mandatory permissions for running with `--daemon` flag. +Mandatory permissions for running with install command. -Optional for service/pod name resolving in non daemon mode +Optional for service/pod name resolving in non install standalone ```yaml - apiGroups: diff --git a/shared/models.go b/shared/models.go index 43fbf6af2..17efc230a 100644 --- a/shared/models.go +++ b/shared/models.go @@ -6,7 +6,6 @@ import ( "github.com/op/go-logging" "github.com/up9inc/mizu/shared/logger" - "github.com/up9inc/mizu/tap/api" v1 "k8s.io/api/core/v1" "gopkg.in/yaml.v3" @@ -34,19 +33,13 @@ type Resources struct { } type MizuAgentConfig struct { - TapTargetRegex api.SerializableRegexp `json:"tapTargetRegex"` - MaxDBSizeBytes int64 `json:"maxDBSizeBytes"` - TargetNamespaces []string `json:"targetNamespaces"` - AgentImage string `json:"agentImage"` - PullPolicy string `json:"pullPolicy"` - LogLevel logging.Level `json:"logLevel"` - IgnoredUserAgents []string `json:"ignoredUserAgents"` - TapperResources Resources `json:"tapperResources"` - MizuResourcesNamespace string `json:"mizuResourceNamespace"` - MizuApiFilteringOptions api.TrafficFilteringOptions `json:"mizuApiFilteringOptions"` - AgentDatabasePath string `json:"agentDatabasePath"` - Istio bool `json:"istio"` - SyncTappers bool `json:"syncTappers"` + MaxDBSizeBytes int64 `json:"maxDBSizeBytes"` + AgentImage string `json:"agentImage"` + PullPolicy string `json:"pullPolicy"` + LogLevel logging.Level `json:"logLevel"` + TapperResources Resources `json:"tapperResources"` + MizuResourcesNamespace string `json:"mizuResourceNamespace"` + AgentDatabasePath string `json:"agentDatabasePath"` } type WebSocketMessageMetadata struct {