Skip to content

Commit 9606752

Browse files
committed
Add a snapshot JSON format for uploads and convert DataReadings to Snapshot format
Signed-off-by: Richard Wall <[email protected]>
1 parent 699a653 commit 9606752

File tree

13 files changed

+380
-57
lines changed

13 files changed

+380
-57
lines changed

api/datareading.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package api
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"time"
7+
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
69
)
710

811
// DataReadingsPost is the payload in the upload request.
@@ -28,8 +31,8 @@ type DataReading struct {
2831
type GatheredResource struct {
2932
// Resource is a reference to a k8s object that was found by the informer
3033
// should be of type unstructured.Unstructured, raw Object
31-
Resource interface{}
32-
DeletedAt Time
34+
Resource interface{} `json:"resource"`
35+
DeletedAt Time `json:"deleted_at,omitempty"`
3336
}
3437

3538
func (v GatheredResource) MarshalJSON() ([]byte, error) {
@@ -48,3 +51,20 @@ func (v GatheredResource) MarshalJSON() ([]byte, error) {
4851

4952
return json.Marshal(data)
5053
}
54+
55+
func (v *GatheredResource) UnmarshalJSON(data []byte) error {
56+
var tmpResource struct {
57+
Resource *unstructured.Unstructured `json:"resource"`
58+
DeletedAt Time `json:"deleted_at,omitempty"`
59+
}
60+
61+
d := json.NewDecoder(bytes.NewReader(data))
62+
d.DisallowUnknownFields()
63+
64+
if err := d.Decode(&tmpResource); err != nil {
65+
return err
66+
}
67+
v.Resource = tmpResource.Resource
68+
v.DeletedAt = tmpResource.DeletedAt
69+
return nil
70+
}

pkg/client/client_cyberark.go

Lines changed: 0 additions & 9 deletions
This file was deleted.

pkg/datagatherer/k8s/discovery.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66

7+
"k8s.io/apimachinery/pkg/version"
78
"k8s.io/client-go/discovery"
89

910
"github.com/jetstack/preflight/pkg/datagatherer"
@@ -57,17 +58,18 @@ func (g *DataGathererDiscovery) WaitForCacheSync(ctx context.Context) error {
5758
return nil
5859
}
5960

61+
type DiscoveryData struct {
62+
ServerVersion *version.Info `json:"server_version"`
63+
}
64+
6065
// Fetch will fetch discovery data from the apiserver, or return an error
6166
func (g *DataGathererDiscovery) Fetch() (interface{}, int, error) {
62-
data, err := g.cl.ServerVersion()
67+
serverVersion, err := g.cl.ServerVersion()
6368
if err != nil {
6469
return nil, -1, fmt.Errorf("failed to get server version: %v", err)
6570
}
6671

67-
response := map[string]interface{}{
68-
// data has type Info: https://godoc.org/k8s.io/apimachinery/pkg/version#Info
69-
"server_version": data,
70-
}
71-
72-
return response, len(response), nil
72+
return &DiscoveryData{
73+
ServerVersion: serverVersion,
74+
}, 1, nil
7375
}

pkg/datagatherer/k8s/dynamic.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -307,14 +307,17 @@ func (g *DataGathererDynamic) WaitForCacheSync(ctx context.Context) error {
307307
return nil
308308
}
309309

310+
type DynamicData struct {
311+
Items []*api.GatheredResource `json:"items"`
312+
}
313+
310314
// Fetch will fetch the requested data from the apiserver, or return an error
311315
// if fetching the data fails.
312316
func (g *DataGathererDynamic) Fetch() (interface{}, int, error) {
313317
if g.groupVersionResource.String() == "" {
314318
return nil, -1, fmt.Errorf("resource type must be specified")
315319
}
316320

317-
var list = map[string]interface{}{}
318321
var items = []*api.GatheredResource{}
319322

320323
fetchNamespaces := g.namespaces
@@ -344,10 +347,9 @@ func (g *DataGathererDynamic) Fetch() (interface{}, int, error) {
344347
return nil, -1, err
345348
}
346349

347-
// add gathered resources to items
348-
list["items"] = items
349-
350-
return list, len(items), nil
350+
return &DynamicData{
351+
Items: items,
352+
}, len(items), nil
351353
}
352354

353355
func redactList(list []*api.GatheredResource, excludeAnnotKeys, excludeLabelKeys []*regexp.Regexp) error {

pkg/datagatherer/k8s/dynamic_test.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -730,15 +730,12 @@ func TestDynamicGatherer_Fetch(t *testing.T) {
730730
}
731731

732732
if tc.expected != nil {
733-
items, ok := res.(map[string]interface{})
733+
data, ok := res.(*DynamicData)
734734
if !ok {
735-
t.Errorf("expected result be an map[string]interface{} but wasn't")
735+
t.Errorf("expected result be *DynamicData but wasn't")
736736
}
737737

738-
list, ok := items["items"].([]*api.GatheredResource)
739-
if !ok {
740-
t.Errorf("expected result be an []*api.GatheredResource but wasn't")
741-
}
738+
list := data.Items
742739
// sorting list of results by name
743740
sortGatheredResources(list)
744741
// sorting list of expected results by name
@@ -1045,10 +1042,9 @@ func TestDynamicGathererNativeResources_Fetch(t *testing.T) {
10451042
}
10461043

10471044
if tc.expected != nil {
1048-
res, ok := rawRes.(map[string]interface{})
1049-
require.Truef(t, ok, "expected result be an map[string]interface{} but wasn't")
1050-
actual := res["items"].([]*api.GatheredResource)
1051-
require.Truef(t, ok, "expected result be an []*api.GatheredResource but wasn't")
1045+
res, ok := rawRes.(*DynamicData)
1046+
require.Truef(t, ok, "expected result be an *DynamicData but wasn't")
1047+
actual := res.Items
10521048

10531049
// sorting list of results by name
10541050
sortGatheredResources(actual)

pkg/datagatherer/k8s/fieldfilter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ var SecretSelectedFields = []FieldPath{
1616
{"metadata", "ownerReferences"},
1717
{"metadata", "selfLink"},
1818
{"metadata", "uid"},
19+
{"metadata", "creationTimestamp"},
20+
{"metadata", "deletionTimestamp"},
21+
{"metadata", "resourceVersion"},
1922

2023
{"type"},
2124
{"data", "tls.crt"},

pkg/internal/cyberark/dataupload/dataupload.go

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import (
1212
"net/http"
1313
"net/url"
1414

15+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1516
"k8s.io/client-go/transport"
1617

1718
"github.com/jetstack/preflight/api"
19+
"github.com/jetstack/preflight/pkg/datagatherer/k8s"
1820
"github.com/jetstack/preflight/pkg/version"
1921
)
2022

@@ -29,6 +31,92 @@ const (
2931
apiPathSnapshotLinks = "/api/ingestions/kubernetes/snapshot-links"
3032
)
3133

34+
type ResourceData map[string][]*unstructured.Unstructured
35+
36+
// Snapshot is the JSON that the CyberArk Discovery and Context API expects to
37+
// be uploaded to the AWS presigned URL.
38+
type Snapshot struct {
39+
AgentVersion string `json:"agent_version"`
40+
ClusterID string `json:"cluster_id"`
41+
K8SVersion string `json:"k8s_version"`
42+
Secrets []*unstructured.Unstructured `json:"secrets"`
43+
ServiceAccounts []*unstructured.Unstructured `json:"service_accounts"`
44+
Roles []*unstructured.Unstructured `json:"roles"`
45+
RoleBindings []*unstructured.Unstructured `json:"role_bindings"`
46+
}
47+
48+
// The names of Datagatherers which have the data to populate the Cyberark Snapshot mapped to the key in the Cyberark snapshot.
49+
var gathererNameToresourceDataKeyMap = map[string]string{
50+
"ark/secrets": "secrets",
51+
"ark/serviceaccounts": "serviceaccounts",
52+
"ark/roles": "roles",
53+
"ark/clusterroles": "roles",
54+
"ark/rolebindings": "rolebindings",
55+
"ark/clusterrolebindings": "rolebindings",
56+
}
57+
58+
func extractResourceListFromReading(reading *api.DataReading) ([]*unstructured.Unstructured, error) {
59+
data, ok := reading.Data.(*k8s.DynamicData)
60+
if !ok {
61+
return nil, fmt.Errorf("failed to convert data: %s", reading.DataGatherer)
62+
}
63+
items := data.Items
64+
resources := make([]*unstructured.Unstructured, len(items))
65+
for i, item := range items {
66+
if resource, ok := item.Resource.(*unstructured.Unstructured); ok {
67+
resources[i] = resource
68+
} else {
69+
return nil, fmt.Errorf("failed to convert resource: %#v", item)
70+
}
71+
}
72+
return resources, nil
73+
}
74+
75+
func extractServerVersionFromReading(reading *api.DataReading) (string, error) {
76+
data, ok := reading.Data.(*k8s.DiscoveryData)
77+
if !ok {
78+
return "", fmt.Errorf("failed to convert data: %s", reading.DataGatherer)
79+
}
80+
if data.ServerVersion == nil {
81+
return "unknown", nil
82+
}
83+
return data.ServerVersion.GitVersion, nil
84+
}
85+
86+
// ConvertDataReadingsToCyberarkSnapshot converts jetstack-secure DataReadings into Cyberark Snapshot format.
87+
func ConvertDataReadingsToCyberarkSnapshot(
88+
payload api.DataReadingsPost,
89+
) (_ *Snapshot, err error) {
90+
k8sVersion := ""
91+
resourceData := ResourceData{}
92+
for _, reading := range payload.DataReadings {
93+
if reading.DataGatherer == "ark/discovery" {
94+
k8sVersion, err = extractServerVersionFromReading(reading)
95+
if err != nil {
96+
return nil, fmt.Errorf("while extracting server version from data-reading: %s", err)
97+
}
98+
}
99+
if key, found := gathererNameToresourceDataKeyMap[reading.DataGatherer]; found {
100+
var resources []*unstructured.Unstructured
101+
resources, err = extractResourceListFromReading(reading)
102+
if err != nil {
103+
return nil, fmt.Errorf("while extracting resource list from data-reading: %s", err)
104+
}
105+
resourceData[key] = append(resourceData[key], resources...)
106+
}
107+
}
108+
109+
return &Snapshot{
110+
AgentVersion: payload.AgentMetadata.Version,
111+
ClusterID: payload.AgentMetadata.ClusterID,
112+
K8SVersion: k8sVersion,
113+
Secrets: resourceData["secrets"],
114+
ServiceAccounts: resourceData["serviceaccounts"],
115+
Roles: resourceData["roles"],
116+
RoleBindings: resourceData["rolebindings"],
117+
}, nil
118+
}
119+
32120
type CyberArkClient struct {
33121
baseURL string
34122
client *http.Client
@@ -63,9 +151,14 @@ func (c *CyberArkClient) PostDataReadingsWithOptions(ctx context.Context, payloa
63151
return fmt.Errorf("programmer mistake: the cluster name (aka `cluster_id` in the config file) cannot be left empty")
64152
}
65153

154+
snapshot, err := ConvertDataReadingsToCyberarkSnapshot(payload)
155+
if err != nil {
156+
return fmt.Errorf("while converting datareadings to Cyberark snapshot format: %s", err)
157+
}
158+
66159
encodedBody := &bytes.Buffer{}
67160
checksum := sha256.New()
68-
if err := json.NewEncoder(io.MultiWriter(encodedBody, checksum)).Encode(payload); err != nil {
161+
if err := json.NewEncoder(io.MultiWriter(encodedBody, checksum)).Encode(snapshot); err != nil {
69162
return err
70163
}
71164

0 commit comments

Comments
 (0)