diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cac081a6c6..69408d9ee6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ### Added -- Add changelog for intelliJ extension, closes [[#542](https://github.com/metalbear-co/mirrord/issues/542)] +- Add changelog for intelliJ extension, closes [#542](https://github.com/metalbear-co/mirrord/issues/542) - Add filter for changelog to ci.yml - Telemetry for intelliJ extension. @@ -17,13 +17,17 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - Update intelliJ extension: lint & bump java version to 17. - Added `/Users` and `/Library` to path to ignore for file operations to improve UX on macOS. +- Use same default options as CLI in intelliJ extension. +- Improve UI layout of intelliJ extension. +- Separate tcp and udp outgoing option in intelliJ extension. - Tighter control of witch environment variables would be passed to the KubeApi when fetching credentials via cli in kube-config. See [#637](https://github.com/metalbear-co/mirrord/issues/637) ### Fixed - Lint Changelog and fix level of a "Changed" tag. - File operations - following symlinks now works as expected. Previously, absolute symlinks lead to use our own path instead of target path. For example, AWS/K8S uses `/var/run/..` for service account credentials. In many machines, `/var/run` is symlink to `/run` so we were using `/run/..` instead of `/proc/{target_pid}/root/run`. - +- Fix not reappearing window after pressing cancel-button in intelliJ extension. + ## 3.3.0 ### Added diff --git a/intellij-ext/src/main/kotlin/com/metalbear/mirrord/LogLevel.kt b/intellij-ext/src/main/kotlin/com/metalbear/mirrord/LogLevel.kt new file mode 100644 index 00000000000..d660197d447 --- /dev/null +++ b/intellij-ext/src/main/kotlin/com/metalbear/mirrord/LogLevel.kt @@ -0,0 +1,5 @@ +package com.metalbear.mirrord + +enum class LogLevel { + ERROR, WARN, INFO, DEBUG, TRACE; +} \ No newline at end of file diff --git a/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDefaultConfig.kt b/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDefaultConfig.kt new file mode 100644 index 00000000000..d13f612ebaa --- /dev/null +++ b/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDefaultConfig.kt @@ -0,0 +1,38 @@ +package com.metalbear.mirrord + +import com.intellij.openapi.application.PathManager +import java.nio.file.Paths + +data class MirrordDefaultConfig( + val ldPreloadPath: String = getSharedLibPath("libmirrord_layer.so"), + val dylibPath: String = getSharedLibPath("libmirrord_layer.dylib"), + val acceptInvalidCertificates: Boolean = true, + val skipProcesses: String = "", + val fileOps: Boolean = true, + val stealTraffic: Boolean = true, + val telemetry: Boolean = true, + val ephemeralContainers: Boolean = false, + val remoteDns: Boolean = false, + val tcpOutgoingTraffic: Boolean = false, + val udpOutgoingTraffic: Boolean = false, + val agentRustLog: LogLevel = LogLevel.INFO, + val rustLog: LogLevel = LogLevel.INFO, + val overrideEnvVarsExclude: String = "", + val overrideEnvVarsInclude: String = "*", +) + +private fun getSharedLibPath(libName: String): String { + val path = Paths.get(PathManager.getPluginsPath(), "mirrord", libName).toString() + + if (System.getProperty("os.name").toLowerCase().contains("win")) { + val wslRegex = "^[a-zA-Z]:".toRegex() + + val wslPath = wslRegex.replace(path) { drive -> + "/mnt/" + drive.value.toLowerCase().removeSuffix(":") + } + + return wslPath.replace("\\", "/") + } + + return path +} diff --git a/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDefaultData.kt b/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDefaultData.kt deleted file mode 100644 index 96ea53a53bb..00000000000 --- a/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDefaultData.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.metalbear.mirrord - -import com.intellij.openapi.application.PathManager -import java.nio.file.Paths - -data class MirrordDefaultData(val ldPreloadPath: String, val dylibPath: String, val agentLog: String, val rustLog: String, val acceptInvalidCertificates: Boolean, val ephemeralContainers: Boolean) { - constructor() : this(getSharedLibPath("libmirrord_layer.so"), getSharedLibPath("libmirrord_layer.dylib"), "ERROR", "ERROR", true, false) -} - -private fun getSharedLibPath(libName: String): String { - val path = Paths.get(PathManager.getPluginsPath(), "mirrord", libName).toString() - - if (System.getProperty("os.name").toLowerCase().contains("win")) { - val wslRegex = "^[a-zA-Z]:".toRegex() - - val wslPath = wslRegex.replace(path) { - drive -> "/mnt/" + drive.value.toLowerCase().removeSuffix(":") - } - - return wslPath.replace("\\", "/") - } - - return path -} diff --git a/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDialogBuilder.kt b/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDialogBuilder.kt index 22e4cf6449a..8576c3ad491 100644 --- a/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDialogBuilder.kt +++ b/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordDialogBuilder.kt @@ -11,100 +11,77 @@ import javax.swing.* import javax.swing.border.EmptyBorder -class MirrordDialogBuilder { - private val dialogHeading: String = "mirrord" - private val podLabel: JLabel = JLabel("Select pod to impersonate") - private val namespaceLabel: JLabel = JLabel("Select Namespace to use") - private val optionLabel: JLabel = JLabel("Toggle Options") +object MirrordDialogBuilder { + private const val dialogHeading: String = "mirrord" + private const val podLabel = "Select Pod" + private const val namespaceLabel = "Select Namespace" + + fun createDialogBuilder(dialogPanel: JPanel): DialogBuilder = DialogBuilder().apply { + setCenterPanel(dialogPanel) + resizable(false) + setTitle(dialogHeading) + } + + fun createMirrordNamespaceDialog(namespaces: JBList): JPanel = JPanel(BorderLayout()).apply { + add(createSelectionDialog(namespaceLabel, namespaces), BorderLayout.CENTER) + } - fun createMirrordKubeDialog( + fun createMirrordConfigDialog( pods: JBList, fileOps: JCheckBox, - remoteDns: JCheckBox, - outgoingTraffic: JCheckBox, + stealTraffic: JCheckBox, telemetry: JCheckBox, - trafficStealing: JCheckBox, - ephemeralCheckbox: JCheckBox, - agentRustLog: JTextField, - rustLog: JTextField, + ephemeralContainer: JCheckBox, + remoteDns: JCheckBox, + tcpOutgoingTraffic: JCheckBox, + udpOutgoingTraffic: JCheckBox, + agentRustLog: JComboBox, + rustLog: JComboBox, excludeEnv: JTextField, includeEnv: JTextField - ): JPanel { - val dialogPanel = JPanel(BorderLayout()) - podLabel.border = EmptyBorder(5, 40, 5, 5) - - val podPanel = JPanel(GridLayout(2, 1, 10, 5)) - podPanel.add(podLabel, BorderLayout.NORTH) - val scrollablePane = JBScrollPane(pods) - podPanel.add(scrollablePane) - - dialogPanel.add(podPanel, BorderLayout.WEST) - - dialogPanel.add( - JSeparator(JSeparator.VERTICAL), - BorderLayout.CENTER - ) - - val optionsPanel = JPanel(GridLayout(11, 1, 10, 2)) - optionLabel.border = EmptyBorder(5, 110, 5, 80) - - optionsPanel.add(optionLabel) - optionsPanel.add(fileOps) - optionsPanel.add(remoteDns) - optionsPanel.add(outgoingTraffic) - optionsPanel.add(telemetry) - optionsPanel.add(trafficStealing) - optionsPanel.add(ephemeralCheckbox) - - val agentLogPanel = JPanel(GridBagLayout()) - agentLogPanel.add(JLabel("Agent Log Level: ")) - agentRustLog.size = Dimension(5, 5) - agentLogPanel.add(agentRustLog) - - agentLogPanel.border = EmptyBorder(10, 10, 10, 10) - - val rustLogPanel = JPanel(GridBagLayout()) - rustLogPanel.add(JLabel("Layer Log Level: ")) - rustLog.size = Dimension(5, 5) - rustLogPanel.add(rustLog) - - rustLogPanel.border = EmptyBorder(10, 10, 10, 10) - - val excludeEnvPanel = JPanel(GridLayout()) - excludeEnvPanel.add(JLabel("Exclude env vars: ")) - excludeEnv.size = Dimension(3, 3) - excludeEnvPanel.add(excludeEnv) - - val includeEnvPanel = JPanel(GridLayout()) - includeEnvPanel.add(JLabel("Include env vars: ")) - excludeEnv.size = Dimension(3, 3) - includeEnvPanel.add(includeEnv) - - optionsPanel.add(agentLogPanel) - optionsPanel.add(rustLogPanel) - optionsPanel.add(excludeEnvPanel) - optionsPanel.add(includeEnvPanel) - - dialogPanel.add(optionsPanel, BorderLayout.EAST) - - return dialogPanel - } - - fun createMirrordNamespaceDialog(namespaces: JBList): JPanel { - val dialogPanel = JPanel(BorderLayout()) - namespaceLabel.border = EmptyBorder(5, 20, 5, 20) - dialogPanel.add(namespaceLabel, BorderLayout.NORTH) - val scrollablePane = JBScrollPane(namespaces) - dialogPanel.add(scrollablePane, BorderLayout.SOUTH) - return dialogPanel + ): JPanel = JPanel(BorderLayout()).apply { + add(createSelectionDialog(podLabel, pods), BorderLayout.WEST) + add(JSeparator(JSeparator.VERTICAL), BorderLayout.CENTER) + add(JPanel(GridLayout(6, 2, 15, 2)).apply { + add(fileOps) + add(stealTraffic) + add(telemetry) + add(ephemeralContainer) + add(remoteDns) + add(JLabel()) // empty label for filling up the row + add(tcpOutgoingTraffic) + add(udpOutgoingTraffic) + add(JPanel(GridBagLayout()).apply { + add(JLabel("Agent Log Level:")) + add(agentRustLog) + }) + add(JPanel(GridBagLayout()).apply { + add(JLabel("Layer Log Level:")) + add(rustLog) + }) + add(JPanel(GridLayout(2, 1)).apply { + add(JLabel("Exclude env vars:")) + add(excludeEnv) + }) + add(JPanel(GridLayout(2, 1)).apply { + add(JLabel("Include env vars:")) + add(includeEnv) + }) + border = EmptyBorder(0, 5, 5, 5) + }, BorderLayout.EAST) } - fun getDialogBuilder(dialogPanel: JPanel): DialogBuilder { - val dialogBuilder = DialogBuilder() - - dialogBuilder.setCenterPanel(dialogPanel) - dialogBuilder.setTitle(dialogHeading) - - return dialogBuilder - } + private fun createSelectionDialog(label: String, items: JBList): JPanel = + JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + border = EmptyBorder(10, 5, 10, 5) + add(JLabel(label).apply { + alignmentX = JLabel.LEFT_ALIGNMENT + }) + add(Box.createRigidArea(Dimension(0, 5))) + add(JBScrollPane(items).apply { + alignmentX = JBScrollPane.LEFT_ALIGNMENT + preferredSize = Dimension(250, 350) + }) + } } \ No newline at end of file diff --git a/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordListener.kt b/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordListener.kt index 82f9b301a49..9f25eb4e8b2 100644 --- a/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordListener.kt +++ b/intellij-ext/src/main/kotlin/com/metalbear/mirrord/MirrordListener.kt @@ -8,23 +8,20 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.ui.DialogWrapper import com.intellij.ui.components.JBList import javax.swing.JCheckBox +import javax.swing.JComboBox import javax.swing.JTextField +import javax.swing.ListSelectionModel -@Suppress("UNCHECKED_CAST", "NAME_SHADOWING") class MirrordListener : ExecutionListener { - init { - val (ldPreloadPath, dylibPath, defaultMirrordAgentLog, rustLog, invalidCertificates, ephemeralContainers) = MirrordDefaultData() - mirrordEnv["DYLD_INSERT_LIBRARIES"] = dylibPath - mirrordEnv["LD_PRELOAD"] = ldPreloadPath - mirrordEnv["MIRRORD_AGENT_RUST_LOG"] = defaultMirrordAgentLog - mirrordEnv["RUST_LOG"] = rustLog - mirrordEnv["MIRRORD_ACCEPT_INVALID_CERTIFICATES"] = invalidCertificates.toString() - mirrordEnv["MIRRORD_EPHEMERAL_CONTAINER"] = ephemeralContainers.toString() - mirrordEnv["MIRRORD_SKIP_PROCESSES"] = "" - mirrordEnv["MIRRORD_OVERRIDE_ENV_VARS_INCLUDE"] = "*" + private val defaults = MirrordDefaultConfig() + init { + mirrordEnv["DYLD_INSERT_LIBRARIES"] = defaults.dylibPath + mirrordEnv["LD_PRELOAD"] = defaults.ldPreloadPath + mirrordEnv["MIRRORD_ACCEPT_INVALID_CERTIFICATES"] = defaults.acceptInvalidCertificates.toString() + mirrordEnv["MIRRORD_SKIP_PROCESSES"] = defaults.skipProcesses } companion object { @@ -38,87 +35,87 @@ class MirrordListener : ExecutionListener { if (enabled && id.isEmpty()) { id = executorId // id is set here to make sure we don't spawn the dialog twice ApplicationManager.getApplication().invokeLater { - val customDialogBuilder = MirrordDialogBuilder() val kubeDataProvider = KubeDataProvider() val namespaces = try { - JBList(kubeDataProvider.getNamespaces()) + kubeDataProvider.getNamespaces().asJBList() } catch (e: Exception) { MirrordEnabler.notify( "Error occurred while fetching namespaces from Kubernetes context", NotificationType.ERROR, env.project ) + // FAILURE: Just call the parent implementation return@invokeLater super.processStartScheduled(executorId, env) } - val panel = customDialogBuilder.createMirrordNamespaceDialog(namespaces) - val dialogBuilder = customDialogBuilder.getDialogBuilder(panel) - dialogBuilder.resizable(false) + val namespaceDialog = MirrordDialogBuilder.createDialogBuilder( + MirrordDialogBuilder.createMirrordNamespaceDialog(namespaces) + ) // SUCCESS: Ask the user for the impersonated pod in the chosen namespace - if (dialogBuilder.show() == DialogWrapper.OK_EXIT_CODE && !namespaces.isSelectionEmpty) { - val choseNamespace = namespaces.selectedValue - + if (namespaceDialog.show() == DialogWrapper.OK_EXIT_CODE && !namespaces.isSelectionEmpty) { val pods = try { - JBList(kubeDataProvider.getNameSpacedPods(choseNamespace)) + kubeDataProvider.getNameSpacedPods(namespaces.selectedValue).asJBList() } catch (e: Exception) { MirrordEnabler.notify( "Error occurred while fetching pods from Kubernetes context", NotificationType.ERROR, env.project ) + // FAILURE: Just call the parent implementation return@invokeLater super.processStartScheduled(executorId, env) } - val fileOps = JCheckBox("File Operations") - fileOps.isSelected = true - val remoteDns = JCheckBox("Remote DNS") - remoteDns.isSelected = true - val outgoingTraffic = JCheckBox("Outgoing Traffic") - outgoingTraffic.isSelected = true - val telemetry = JCheckBox("Telemetry") - telemetry.isSelected = true - val trafficStealing = JCheckBox("Traffic Stealing") - - val ephemeralContainerCheckBox = JCheckBox("Enable Ephemeral Containers") - - val agentRustLog = JTextField(mirrordEnv["MIRRORD_AGENT_RUST_LOG"]) - val rustLog = JTextField(mirrordEnv["RUST_LOG"]) - val excludeEnv = JTextField("") - val includeEnv = JTextField(mirrordEnv["MIRRORD_OVERRIDE_ENV_VARS_INCLUDE"]) - - val panel = customDialogBuilder.createMirrordKubeDialog( - pods, - fileOps, - remoteDns, - outgoingTraffic, - telemetry, - trafficStealing, - ephemeralContainerCheckBox, - agentRustLog, - rustLog, - excludeEnv, - includeEnv, + val fileOps = JCheckBox("File Operations", defaults.fileOps) + val stealTraffic = JCheckBox("TCP Steal Traffic", defaults.stealTraffic) + val telemetry = JCheckBox("Telemetry", defaults.telemetry) + val ephemeralContainer = JCheckBox("Use Ephemeral Container", defaults.ephemeralContainers) + val remoteDns = JCheckBox("Remote DNS", defaults.remoteDns) + val tcpOutgoingTraffic = JCheckBox("TCP Outgoing Traffic", defaults.tcpOutgoingTraffic) + val udpOutgoingTraffic = JCheckBox("UDP Outgoing Traffic", defaults.udpOutgoingTraffic) + + + val agentRustLog = JComboBox(LogLevel.values()) + agentRustLog.selectedItem = defaults.agentRustLog + val rustLog = JComboBox(LogLevel.values()) + rustLog.selectedItem = defaults.rustLog + + val excludeEnv = JTextField(defaults.overrideEnvVarsExclude) + val includeEnv = JTextField(defaults.overrideEnvVarsInclude) + + val mirrordConfigDialog = MirrordDialogBuilder.createDialogBuilder( + MirrordDialogBuilder.createMirrordConfigDialog( + pods, + fileOps, + stealTraffic, + telemetry, + ephemeralContainer, + remoteDns, + tcpOutgoingTraffic, + udpOutgoingTraffic, + agentRustLog, + rustLog, + excludeEnv, + includeEnv, + ) ) - val dialogBuilder = customDialogBuilder.getDialogBuilder(panel) - dialogBuilder.resizable(false) // SUCCESS: set the respective environment variables - if (dialogBuilder.show() == DialogWrapper.OK_EXIT_CODE && !pods.isSelectionEmpty) { - mirrordEnv["MIRRORD_IMPERSONATED_TARGET"] = "pod/${pods.selectedValue as String}" - mirrordEnv["MIRRORD_TARGET_NAMESPACE"] = choseNamespace as String + if (mirrordConfigDialog.show() == DialogWrapper.OK_EXIT_CODE && !pods.isSelectionEmpty) { + mirrordEnv["MIRRORD_IMPERSONATED_TARGET"] = "pod/${pods.selectedValue}" + mirrordEnv["MIRRORD_TARGET_NAMESPACE"] = namespaces.selectedValue mirrordEnv["MIRRORD_FILE_OPS"] = fileOps.isSelected.toString() - mirrordEnv["MIRRORD_AGENT_TCP_STEAL_TRAFFIC"] = trafficStealing.isSelected.toString() - mirrordEnv["MIRRORD_EPHEMERAL_CONTAINER"] = ephemeralContainerCheckBox.isSelected.toString() + mirrordEnv["MIRRORD_AGENT_TCP_STEAL_TRAFFIC"] = stealTraffic.isSelected.toString() + mirrordEnv["MIRRORD_EPHEMERAL_CONTAINER"] = ephemeralContainer.isSelected.toString() mirrordEnv["MIRRORD_REMOTE_DNS"] = remoteDns.isSelected.toString() - mirrordEnv["MIRRORD_TCP_OUTGOING"] = outgoingTraffic.isSelected.toString() - mirrordEnv["MIRRORD_UDP_OUTGOING"] = outgoingTraffic.isSelected.toString() - mirrordEnv["RUST_LOG"] = rustLog.text.toString() - mirrordEnv["MIRRORD_AGENT_RUST_LOG"] = agentRustLog.text.toString() - if (excludeEnv.text.toString().isNotEmpty()) { + mirrordEnv["MIRRORD_TCP_OUTGOING"] = tcpOutgoingTraffic.isSelected.toString() + mirrordEnv["MIRRORD_UDP_OUTGOING"] = udpOutgoingTraffic.isSelected.toString() + mirrordEnv["RUST_LOG"] = (rustLog.selectedItem as LogLevel).name + mirrordEnv["MIRRORD_AGENT_RUST_LOG"] = (agentRustLog.selectedItem as LogLevel).name + if (excludeEnv.text.isNotEmpty()) { mirrordEnv["MIRRORD_OVERRIDE_ENV_VARS_EXCLUDE"] = excludeEnv.text.toString() } - if (includeEnv.text.toString().isNotEmpty()) { + if (includeEnv.text.isNotEmpty()) { mirrordEnv["MIRRORD_OVERRIDE_ENV_VARS_INCLUDE"] = includeEnv.text.toString() } if (!telemetry.isSelected) { @@ -129,6 +126,8 @@ class MirrordListener : ExecutionListener { envSet = envMap != null } + } else { + id = "" } } } @@ -136,6 +135,11 @@ class MirrordListener : ExecutionListener { return super.processStartScheduled(executorId, env) } + private fun List.asJBList() = JBList(this).apply { + selectionMode = ListSelectionModel.SINGLE_SELECTION + } + + @Suppress("UNCHECKED_CAST") override fun processTerminating(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) { // NOTE: If the option was enabled, and we actually set the env, i.e. cancel was not clicked on the dialog, // we clear up the Environment, because we don't want mirrord to run again if the user hits debug again @@ -166,6 +170,7 @@ class MirrordListener : ExecutionListener { return super.processTerminating(executorId, env, handler) } + @Suppress("UNCHECKED_CAST") private fun getRunConfigEnv(env: ExecutionEnvironment): LinkedHashMap? { if (env.runProfile::class.simpleName == "GoApplicationConfiguration") return null