Add initial support for kubequery (#6863)

Configuration and fixes for the Fleet server and frontend to add support
for https://github.com/Uptycs/kubequery.

Co-authored-by: Michal Nicpon <michal@fleetdm.com>
This commit is contained in:
Zach Wasserman
2022-09-15 10:02:17 -06:00
committed by GitHub
co-authored by Michal Nicpon
parent 0d16974fd6
commit 486b67caca
7 changed files with 226 additions and 6 deletions
+1
View File
@@ -0,0 +1 @@
* Add support for [kubequery](https://github.com/Uptycs/kubequery).
+1 -1
View File
@@ -584,7 +584,7 @@ export const inMilliseconds = (nanoseconds: number): number => {
export const humanHostLastRestart = (
detailUpdatedAt: string,
uptime: number
uptime: number | string
): string => {
if (
!detailUpdatedAt ||
+1
View File
@@ -495,6 +495,7 @@ func verifyDiscovery(t *testing.T, queries, discovery map[string]string) {
hostDetailQueryPrefix + "mdm": {},
hostDetailQueryPrefix + "munki_info": {},
hostDetailQueryPrefix + "windows_update_history": {},
hostDetailQueryPrefix + "kubequery_info": {},
}
for name := range queries {
require.NotEmpty(t, discovery[name])
+23 -1
View File
@@ -24,7 +24,7 @@ import (
type DetailQuery struct {
// Query is the SQL query string.
Query string
// Discovery is the SQL query that defines whether the query will run or the host or not.
// Discovery is the SQL query that defines whether the query will run on the host or not.
// If not set, Fleet makes sure the query will always run.
Discovery string
// Platforms is a list of platforms to run the query on. If this value is
@@ -347,6 +347,11 @@ FROM logical_drives WHERE file_system = 'NTFS' LIMIT 1;`,
Platforms: []string{"windows"},
IngestFunc: ingestDiskSpace,
},
"kubequery_info": {
Query: `SELECT * from kubernetes_info`,
IngestFunc: ingestKubequeryInfo,
Discovery: discoveryTable("kubernetes_info"),
},
}
// extraDetailQueries defines extra detail queries that should be run on the host, as
@@ -1037,6 +1042,9 @@ func directIngestUsers(ctx context.Context, logger log.Logger, host *fleet.Host,
}
users = append(users, u)
}
if len(users) == 0 {
return nil
}
if err := ds.SaveHostUsers(ctx, host.ID, users); err != nil {
return ctxerr.Wrap(ctx, err, "update host users")
}
@@ -1106,6 +1114,20 @@ func directIngestMunkiInfo(ctx context.Context, logger log.Logger, host *fleet.H
return ds.SetOrUpdateMunkiInfo(ctx, host.ID, rows[0]["version"], errList, warnList)
}
func ingestKubequeryInfo(ctx context.Context, logger log.Logger, host *fleet.Host, rows []map[string]string) error {
if len(rows) != 1 {
logger.Log("component", "service", "method", "ingestKubequeryInfo", "warn",
fmt.Sprintf("kubernetes_info expected single result got %d", len(rows)))
}
host.Hostname = fmt.Sprintf("kubequery %s", rows[0]["cluster_name"])
// These values are not provided by kubequery
host.OsqueryVersion = "kubequery"
host.Platform = "kubequery"
return nil
}
func GetDetailQueries(fleetConfig config.FleetConfig, features *fleet.Features) map[string]DetailQuery {
generatedMap := make(map[string]DetailQuery)
for key, query := range hostDetailQueries {
+5 -4
View File
@@ -297,7 +297,7 @@ func sortedKeysCompare(t *testing.T, m map[string]DetailQuery, expectedKeys []st
func TestGetDetailQueries(t *testing.T) {
queriesNoConfig := GetDetailQueries(config.FleetConfig{}, nil)
require.Len(t, queriesNoConfig, 17)
require.Len(t, queriesNoConfig, 18)
baseQueries := []string{
"network_interface",
@@ -317,18 +317,19 @@ func TestGetDetailQueries(t *testing.T) {
"os_windows",
"os_unix_like",
"windows_update_history",
"kubequery_info",
}
sortedKeysCompare(t, queriesNoConfig, baseQueries)
queriesWithoutWinOSVuln := GetDetailQueries(config.FleetConfig{Vulnerabilities: config.VulnerabilitiesConfig{DisableWinOSVulnerabilities: true}}, nil)
require.Len(t, queriesWithoutWinOSVuln, 16)
require.Len(t, queriesWithoutWinOSVuln, 17)
queriesWithUsers := GetDetailQueries(config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, &fleet.Features{EnableHostUsers: true})
require.Len(t, queriesWithUsers, 19)
require.Len(t, queriesWithUsers, 20)
sortedKeysCompare(t, queriesWithUsers, append(baseQueries, "users", "scheduled_query_stats"))
queriesWithUsersAndSoftware := GetDetailQueries(config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, &fleet.Features{EnableHostUsers: true, EnableSoftwareInventory: true})
require.Len(t, queriesWithUsersAndSoftware, 22)
require.Len(t, queriesWithUsersAndSoftware, 23)
sortedKeysCompare(t, queriesWithUsersAndSoftware,
append(baseQueries, "users", "software_macos", "software_linux", "software_windows", "scheduled_query_stats"))
}
+14
View File
@@ -0,0 +1,14 @@
# Kubequery and Fleet
Use the provided configuration file ([kubequery-fleet.yml](kubequery-fleet.yml)) to get a [kubequery](https://github.com/Uptycs/kubequery) instance connected to Fleet.
Before deploying, first retrieve the enroll secret from Fleet by opening a web browser to the Fleet URL, going to the Hosts page, and clicking on the "Manage enroll secret" button.
Alternatively, you can get the enroll secret using `fleetctl` using `fleetctl get enroll-secret`.
Update the `enroll.secret` in the `ConfigMap`. In production, you will also need to update the `tls_hostname` and `fleet.pem` to the appropriate values. Finally, deploy kubequery using `kubectl`
```sh
kubectl apply -f kubequery-fleet.yml
```
Kubernetes clusters will show up in Fleet with hostnames like `kubequery <CLUSTER NAME>`.
+181
View File
@@ -0,0 +1,181 @@
# Copyright (c) 2020-present, The kubequery authors
# Copyright (c) 2022-present, Fleet
#
# This source code is licensed as defined by the LICENSE file found in the
# root directory of this source tree.
#
# SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
# Adapted from original provided by Kubequery project.
---
apiVersion: v1
kind: Namespace
metadata:
name: kubequery
labels:
app.kubernetes.io/name: kubequery
app.kubernetes.io/part-of: kubequery
app.kubernetes.io/version: latest
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubequery-sa
namespace: kubequery
labels:
app.kubernetes.io/name: kubequery-sa
app.kubernetes.io/part-of: kubequery
app.kubernetes.io/version: latest
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kubequery-clusterrole
labels:
app.kubernetes.io/name: kubequery-clusterrole
app.kubernetes.io/part-of: kubequery
app.kubernetes.io/version: latest
rules:
- apiGroups: ["", "admissionregistration.k8s.io", "apps", "autoscaling", "batch", "events.k8s.io", "networking.k8s.io", "policy", "rbac.authorization.k8s.io", "storage.k8s.io"]
resources: ["*"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubequery-clusterrolebinding
labels:
app.kubernetes.io/name: kubequery-clusterrolebinding
app.kubernetes.io/part-of: kubequery
app.kubernetes.io/version: latest
roleRef:
kind: ClusterRole
name: kubequery-clusterrole
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: kubequery-sa
namespace: kubequery
---
apiVersion: v1
kind: ConfigMap
metadata:
name: kubequery-config
namespace: kubequery
labels:
app.kubernetes.io/name: kubequery-config
app.kubernetes.io/part-of: kubequery
app.kubernetes.io/version: latest
data:
enroll.secret: TODO
kubequery.flags: |
# Server
--tls_hostname=host.docker.internal:8080
--tls_server_certs=/opt/uptycs/etc/fleet.pem
# Enrollment
--enroll_secret_path=/opt/uptycs/etc/enroll.secret
--enroll_tls_endpoint=/api/v1/osquery/enroll
# Configuration
--config_plugin=tls
--config_tls_endpoint=/api/v1/osquery/config
--config_refresh=10
# Live query
--disable_distributed=false
--distributed_plugin=tls
--distributed_interval=10
--distributed_tls_max_attempts=3
--distributed_tls_read_endpoint=/api/v1/osquery/distributed/read
--distributed_tls_write_endpoint=/api/v1/osquery/distributed/write
# Logging
--logger_plugin=tls
--logger_tls_endpoint=/api/v1/osquery/log
--logger_tls_period=10
# File carving
--disable_carver=false
--carver_start_endpoint=/api/v1/osquery/carve/begin
--carver_continue_endpoint=/api/v1/osquery/carve/block
--carver_block_size=2000000
kubequery.conf: |
fleet.pem: |
-----BEGIN CERTIFICATE-----
MIIE5jCCAs6gAwIBAgIJAKq0+FAVArUhMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV
BAMMCkZsZWV0IFRlc3QwHhcNMjAxMDE1MTg1ODE5WhcNMzAwNzE1MTg1ODE5WjAV
MRMwEQYDVQQDDApGbGVldCBUZXN0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAttQ62lpMq48/XjQFxYg47D2fgKgTBMjDNSfCt9VpqE3xPnybmWo8VZtk
jmrFM50IhagyYSjvl9iqdrnsl3ZV8KYbWEy6849zDYF1SudmC7/pJyH7QvpKgL7V
4McM62jM905hyFy9KZTAlFiaeezWSWJre7kHsK2u5tsqS6ElatEZmF59sInixaRw
RqVxOhtDm7Sl1c5xKWM6phWoJfykspFpu5J6N2jRQXCfzBYoQWN76OohGouit/BQ
C1xvm+f7wgGZgbbfDjUoHAe9Yhd3XzZsYTgMDt/SRJRDnxFZwo8BAkY9yJm7f3dQ
AEhgJ66KbyoxITdgma1hgmeWibZY4hVymcRxB1B3RNN2at19RNy2J+brMxlG0KZk
nD77EqidrwLAlYcdeU3yLjt0vYPxT+RW7l1jiZlVi/oaykAmVfOhWnTnTwbsYs7O
UMyMyYHQECEs98ex7wrjThIBJScqhsSN1ipAxr5RgaDr+U5IR+tLhMewBy8So+nf
2YuMhLfkCgoY80ELhz5F8avts5hksB0hqnNYr+Nlwm6eXqEPZSzFJmdc1IbmWzq2
7UH1OQmBFF2qr2j/8dcM+oPNgjrEEQjtyW0S4j2PhjSEbINgcwu0AaABssLI80Vm
Gp1TjUGA92rMwIjlAtcUUB5FOKSS8vAXb1VcDWMkybh9sHj4Z0ECAwEAAaM5MDcw
NQYDVR0RBC4wLIIJbG9jYWxob3N0ghRob3N0LmRvY2tlci5pbnRlcm5hbIIJMTI3
LjAuMC4xMA0GCSqGSIb3DQEBCwUAA4ICAQBZOY++LNRTVG8XlQGVlOloEKA2WY3P
gXKJLSM7xWSxj2co0v+noyhoInHT7ysub8en59Et+vN53/0OobxNIdEKDUdqs38R
F++Oy6s/MhFHSo87F06t+W91/60ub4iFRHfev1qeNFV6Yzv9sFJ5LpXLFk+qVDb8
pPyFFE1bXjctDPjD5gUj+Y34XikVKzMb7xddWCNs34v1KCaCBW7kkfefxiZiDR6g
lCEkDzp6xaLS898oCbfFakjr4bvOgBP1IqXLIDLPMhivaxNAooHTtu/3ezp7puix
TSDkjlkStDtEFw/wjyaMcEkk51Gs1ponBbADLRxQ50AHDWk/4vy8GcIVc6CdVEOA
Zw12FN06C4Jviiiv6uCXZ6iZ+V+pjGiGmSNYF+kruUs8BfrJIB89lqxpdQ4Kx01j
AuSFvjRRvIPmvApSdKEjLcY3AYRivXsB/hASMBbjh/p1f/JzSJdxoqSvONhNQJuh
+wcdNVQhGAv3kkLn/HMHTBl2Ur+9tQaJrnR1tWl1IzwLRJIi0Soyp/q5ZjQyFglj
32xW83DZhtpQ2SI1QGy4AvWIPnGHZhMfav02KnKRhZdOMW4oekXRMrwiyXCqIazc
xXzAlCq8dHdP2Y9uvfFxVFyE+uSfkcPxX+DG/ZnpgCS27oKA/qLCybJamlqtveNs
RSjNe5qwGi0ifA==
-----END CERTIFICATE-----
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubequery
namespace: kubequery
labels:
app.kubernetes.io/name: kubequery
app.kubernetes.io/part-of: kubequery
app.kubernetes.io/version: latest
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: kubequery
template:
metadata:
labels:
app.kubernetes.io/name: kubequery
app.kubernetes.io/part-of: kubequery
app.kubernetes.io/version: latest
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
terminationGracePeriodSeconds: 10
serviceAccountName: kubequery-sa
containers:
- name: kubequery
image: uptycs/kubequery:latest
imagePullPolicy: Always
resources:
requests:
cpu: 200m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
volumeMounts:
- name: config
mountPath: /opt/uptycs/config
volumes:
- name: config
configMap:
name: kubequery-config