diff --git a/Mythic_CLI/src/cmd/config/env.go b/Mythic_CLI/src/cmd/config/env.go index 097e5bcb..d87bd3f2 100644 --- a/Mythic_CLI/src/cmd/config/env.go +++ b/Mythic_CLI/src/cmd/config/env.go @@ -110,6 +110,12 @@ func setMythicConfigDefaultValues() { mythicEnv.SetDefault("global_restart_policy", "always") mythicEnvInfo["global_restart_policy"] = `This sets the restart policy for the containers within Mythic. Valid options should only be 'always', 'unless-stopped', and 'on-failure'. The default of 'always' will ensure that Mythic comes back up even when the server reboots. The 'unless-stopped' value means that Mythic should come back online after reboot unless you specifically ran './mythic-cli stop' first.` + mythicEnv.SetDefault("mythic_docker_image_prefix", "ghcr.io/its-a-feature") + mythicEnvInfo["mythic_docker_image_prefix"] = `This sets the registry + namespace prefix used to pull the core Mythic service images (mythic_server, mythic_postgres, mythic_react, mythic_documentation, mythic_graphql, mythic_rabbitmq, mythic_nginx, mythic_jupyter). The image used for each service will be /:. Set this to an internal/custom registry+namespace (for example, "myregistry.local/mythic") to pull the core images from there instead of GitHub's Container Registry. This setting has no effect on services where *_use_build_context is true. The default of "ghcr.io/its-a-feature" preserves the original GitHub-hosted image location.` + + mythicEnv.SetDefault("mythic_agent_docker_image_prefix", "") + mythicEnvInfo["mythic_agent_docker_image_prefix"] = `This sets the registry + namespace prefix used to pull images for installed agents and C2 profiles. When this is empty (the default), each agent's image URL from its config.json's "remote_images" map is used verbatim. When this is set to a non-empty value, every URL declared in an agent's "remote_images" has everything before the trailing image[:tag|@digest] segment replaced with this prefix before being stored in _remote_image. The same rewrite is also re-applied each time the docker-compose entry is generated, so changing this value and running start/build will switch the image without re-installing. Set this to an internal/custom registry+namespace (for example, "myregistry.local/agents") to pull installed agent images from there.` + // nginx configuration --------------------------------------------- mythicEnv.SetDefault("nginx_port", 7443) mythicEnvInfo["nginx_port"] = `This sets the port used for the Nginx reverse proxy - this port is used by the React UI and Mythic's Scripting` diff --git a/Mythic_CLI/src/cmd/internal/installservice.go b/Mythic_CLI/src/cmd/internal/installservice.go index 12921c31..1373b9c5 100644 --- a/Mythic_CLI/src/cmd/internal/installservice.go +++ b/Mythic_CLI/src/cmd/internal/installservice.go @@ -39,8 +39,9 @@ func InstallFolder(installPath string, overWrite bool, keepVolume bool, installU } } latestVersion := installConfig.GetStringMapString("remote_images") + agentImagePrefix := config.GetMythicEnv().GetString("mythic_agent_docker_image_prefix") for key, val := range latestVersion { - config.SetNewConfigStrings(fmt.Sprintf("%s_remote_image", key), val) + config.SetNewConfigStrings(fmt.Sprintf("%s_remote_image", key), rewriteImagePrefix(val, agentImagePrefix)) if !config.GetMythicEnv().InConfig(fmt.Sprintf("%s_use_volume", key)) { config.SetNewConfigStrings(fmt.Sprintf("%s_use_volume", key), "false") } diff --git a/Mythic_CLI/src/cmd/internal/serviceMetadata.go b/Mythic_CLI/src/cmd/internal/serviceMetadata.go index 155c1ec4..b82dbaa3 100644 --- a/Mythic_CLI/src/cmd/internal/serviceMetadata.go +++ b/Mythic_CLI/src/cmd/internal/serviceMetadata.go @@ -44,7 +44,7 @@ func AddMythicService(service string, removeVolume bool) { } pStruct["image"] = service } else { - pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")) + pStruct["image"] = fmt.Sprintf("%s/%s:%s", mythicEnv.GetString("mythic_docker_image_prefix"), service, mythicEnv.GetString("global_docker_latest")) } pStruct["cpus"] = mythicEnv.GetInt("POSTGRES_CPUS") @@ -126,7 +126,7 @@ func AddMythicService(service string, removeVolume bool) { } pStruct["image"] = service } else { - pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")) + pStruct["image"] = fmt.Sprintf("%s/%s:%s", mythicEnv.GetString("mythic_docker_image_prefix"), service, mythicEnv.GetString("global_docker_latest")) } if mythicEnv.GetString("mythic_docker_networking") == "bridge" { if mythicEnv.GetBool("documentation_bind_localhost_only") { @@ -200,7 +200,7 @@ func AddMythicService(service string, removeVolume bool) { } pStruct["image"] = service } else { - pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")) + pStruct["image"] = fmt.Sprintf("%s/%s:%s", mythicEnv.GetString("mythic_docker_image_prefix"), service, mythicEnv.GetString("global_docker_latest")) } pStruct["cpus"] = mythicEnv.GetInt("HASURA_CPUS") @@ -288,7 +288,7 @@ func AddMythicService(service string, removeVolume bool) { } pStruct["image"] = service } else { - pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")) + pStruct["image"] = fmt.Sprintf("%s/%s:%s", mythicEnv.GetString("mythic_docker_image_prefix"), service, mythicEnv.GetString("global_docker_latest")) } nginxUseSSL := "ssl" @@ -393,7 +393,7 @@ func AddMythicService(service string, removeVolume bool) { } pStruct["image"] = service } else { - pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")) + pStruct["image"] = fmt.Sprintf("%s/%s:%s", mythicEnv.GetString("mythic_docker_image_prefix"), service, mythicEnv.GetString("global_docker_latest")) } pStruct["cpus"] = mythicEnv.GetInt("RABBITMQ_CPUS") if mythicEnv.GetString("rabbitmq_mem_limit") != "" { @@ -502,7 +502,7 @@ func AddMythicService(service string, removeVolume bool) { } pStruct["image"] = service } else { - pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")) + pStruct["image"] = fmt.Sprintf("%s/%s:%s", mythicEnv.GetString("mythic_docker_image_prefix"), service, mythicEnv.GetString("global_docker_latest")) } if !mythicEnv.GetBool("mythic_react_use_volume") { @@ -592,7 +592,7 @@ func AddMythicService(service string, removeVolume bool) { } pStruct["image"] = service } else { - pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")) + pStruct["image"] = fmt.Sprintf("%s/%s:%s", mythicEnv.GetString("mythic_docker_image_prefix"), service, mythicEnv.GetString("global_docker_latest")) } pStruct["cpus"] = mythicEnv.GetInt("JUPYTER_CPUS") @@ -680,7 +680,7 @@ func AddMythicService(service string, removeVolume bool) { } pStruct["image"] = service } else { - pStruct["image"] = fmt.Sprintf("ghcr.io/its-a-feature/%s:%s", service, mythicEnv.GetString("global_docker_latest")) + pStruct["image"] = fmt.Sprintf("%s/%s:%s", mythicEnv.GetString("mythic_docker_image_prefix"), service, mythicEnv.GetString("global_docker_latest")) } pStruct["cpus"] = mythicEnv.GetInt("MYTHIC_SERVER_CPUS") @@ -894,7 +894,7 @@ func Add3rdPartyService(service string, additionalConfigs map[string]interface{} if useBuildContext, ok := agentConfigs[agentUseBuildContextKey]; ok { if useBuildContext == "false" { delete(existingConfig, "build") - existingConfig["image"] = agentConfigs[agentRemoteImageKey] + existingConfig["image"] = rewriteImagePrefix(agentConfigs[agentRemoteImageKey], config.GetMythicEnv().GetString("mythic_agent_docker_image_prefix")) } } if useVolume, ok := agentConfigs[agentUseVolumeKey]; ok { @@ -984,3 +984,14 @@ func Initialize() { } } } + +// rewriteImagePrefix replaces everything before the trailing image[:tag|@digest] +// segment of imageURL with newPrefix. An empty newPrefix returns imageURL unchanged. +func rewriteImagePrefix(imageURL, newPrefix string) string { + newPrefix = strings.TrimRight(newPrefix, "/") + if newPrefix == "" { + return imageURL + } + parts := strings.Split(imageURL, "/") + return newPrefix + "/" + parts[len(parts)-1] +}