-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new subcommand to dump Agent Policies #862
Changes from 5 commits
4b3a1c6
e8be6c8
ab1f310
469fe8c
9bb50ff
c33295d
d813be9
c175000
62e51fc
80a61b0
898f15f
fb226d9
7e17639
862883f
d126235
47fcf74
0bbcc90
c9af623
5aa71f3
3fa942b
2184cec
58487b5
3334135
d872ec2
ac2b8ca
7251ad6
cc1a245
eb1fe46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ import ( | |
"github.com/elastic/elastic-package/internal/cobraext" | ||
"github.com/elastic/elastic-package/internal/dump" | ||
"github.com/elastic/elastic-package/internal/elasticsearch" | ||
"github.com/elastic/elastic-package/internal/kibana" | ||
) | ||
|
||
const dumpLongDescription = `Use this command as a exploratory tool to dump assets relevant for the package.` | ||
|
@@ -19,30 +20,43 @@ const dumpInstalledObjectsLongDescription = `Use this command to dump objects in | |
|
||
Use this command as a exploratory tool to dump objects as they are installed by Fleet when installing a package. Dumped objects are stored in files as they are returned by APIs of the stack, without any processing.` | ||
|
||
const dumpAgentPoliciesLongDescription = `Use this command to dump agent policies created by Fleet as part of a package installation. | ||
|
||
Use this command as a exploratory tool to dump agent policies as they are created by Fleet when installing a package. Dumped agent policies are stored in files as they are returned by APIs of the stack, without any processing.` | ||
|
||
func setupDumpCommand() *cobraext.Command { | ||
dumpInstalledObjectsCmd := &cobra.Command{ | ||
Use: "installed-objects", | ||
Short: "Dump objects installed in the stack", | ||
Long: dumpInstalledObjectsLongDescription, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Maybe we can add more guidance to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, I've updated the dump description as:
Would you add anything else here? |
||
RunE: dumpInstalledObjectsCmd, | ||
RunE: dumpInstalledObjectsCmdAction, | ||
} | ||
dumpInstalledObjectsCmd.Flags().Bool(cobraext.TLSSkipVerifyFlagName, false, cobraext.TLSSkipVerifyFlagDescription) | ||
|
||
dumpAgentPoliciesCmd := &cobra.Command{ | ||
Use: "agent-policies", | ||
Short: "Dump agent policies defined in the stack", | ||
Long: dumpAgentPoliciesLongDescription, | ||
RunE: dumpAgentPoliciesCmdAction, | ||
} | ||
dumpAgentPoliciesCmd.Flags().StringP(cobraext.AgentPolicyFlagName, "", "", cobraext.AgentPolicyDescription) | ||
|
||
cmd := &cobra.Command{ | ||
Use: "dump", | ||
Short: "Dump package assets", | ||
Long: dumpLongDescription, | ||
} | ||
cmd.PersistentFlags().StringP(cobraext.PackageFlagName, cobraext.PackageFlagShorthand, "", cobraext.PackageFlagDescription) | ||
cmd.MarkFlagRequired(cobraext.PackageFlagName) | ||
cmd.MarkFlagRequired(cobraext.PackageFlagName) // TODO: required for dumping agent policies? | ||
cmd.PersistentFlags().StringP(cobraext.DumpOutputFlagName, "o", "package-dump", cobraext.DumpOutputFlagDescription) | ||
|
||
cmd.AddCommand(dumpInstalledObjectsCmd) | ||
cmd.AddCommand(dumpAgentPoliciesCmd) | ||
|
||
return cobraext.NewCommand(cmd, cobraext.ContextGlobal) | ||
} | ||
|
||
func dumpInstalledObjectsCmd(cmd *cobra.Command, args []string) error { | ||
func dumpInstalledObjectsCmdAction(cmd *cobra.Command, args []string) error { | ||
packageName, err := cmd.Flags().GetString(cobraext.PackageFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.PackageFlagName) | ||
|
@@ -76,3 +90,56 @@ func dumpInstalledObjectsCmd(cmd *cobra.Command, args []string) error { | |
cmd.Printf("Dumped %d installed objects for package %s to %s\n", n, packageName, outputPath) | ||
return nil | ||
} | ||
|
||
func dumpAgentPoliciesCmdAction(cmd *cobra.Command, args []string) error { | ||
packageName, err := cmd.Flags().GetString(cobraext.PackageFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.PackageFlagName) | ||
} | ||
|
||
agentPolicy, err := cmd.Flags().GetString(cobraext.AgentPolicyFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.AgentPolicyFlagName) | ||
} | ||
|
||
outputPath, err := cmd.Flags().GetString(cobraext.DumpOutputFlagName) | ||
if err != nil { | ||
return cobraext.FlagParsingError(err, cobraext.DumpOutputFlagName) | ||
} | ||
|
||
tlsSkipVerify, _ := cmd.Flags().GetBool(cobraext.TLSSkipVerifyFlagName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. side question to @jsoriano: With your change enforcing SSL mode, will such options (like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this flag should continue working after the changes in #847. |
||
|
||
var clientOptions []kibana.ClientOption | ||
if tlsSkipVerify { | ||
clientOptions = append(clientOptions, kibana.TLSSkipVerify()) | ||
} | ||
kibanaClient, err := kibana.NewClient(clientOptions...) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to initialize Kibana client") | ||
} | ||
|
||
switch { | ||
case agentPolicy != "": | ||
jsoriano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
dumper := dump.NewAgentPolicyDumper(kibanaClient, agentPolicy) | ||
err = dumper.DumpAgentPolicy(cmd.Context(), outputPath) | ||
if err != nil { | ||
return errors.Wrap(err, "dump failed") | ||
} | ||
cmd.Printf("Dumped agent policy %s to %s\n", agentPolicy, outputPath) | ||
case packageName != "": | ||
dumper := dump.NewAgentPoliciesDumper(kibanaClient) | ||
count, err := dumper.DumpAgentPoliciesFileteredByPackage(cmd.Context(), packageName, outputPath) | ||
if err != nil { | ||
return errors.Wrap(err, "dump failed") | ||
} | ||
cmd.Printf("Dumped %d agent policies filtering by package name %s to %s\n", count, packageName, outputPath) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit. Log a different message if no policies are dumped. It prints now:
|
||
default: | ||
dumper := dump.NewAgentPoliciesDumper(kibanaClient) | ||
count, err := dumper.DumpAll(cmd.Context(), outputPath) | ||
if err != nil { | ||
return errors.Wrap(err, "dump failed") | ||
} | ||
cmd.Printf("Dumped %d agent policies to %s\n", count, outputPath) | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
// or more contributor license agreements. Licensed under the Elastic License; | ||
// you may not use this file except in compliance with the Elastic License. | ||
|
||
package dump | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"path/filepath" | ||
|
||
"github.com/elastic/elastic-package/internal/common" | ||
"github.com/elastic/elastic-package/internal/kibana" | ||
) | ||
|
||
const AgentPoliciesDumpDir = "agent_policies" | ||
|
||
type AgentPolicyDumper struct { | ||
name string | ||
client *kibana.Client | ||
|
||
policy *AgentPolicy | ||
} | ||
|
||
type AgentPoliciesDumper struct { | ||
jsoriano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
client *kibana.Client | ||
|
||
policies []AgentPolicy | ||
jsoriano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
type AgentPolicy struct { | ||
name string | ||
raw json.RawMessage | ||
} | ||
|
||
func (p AgentPolicy) Name() string { | ||
return p.name | ||
} | ||
|
||
func (p AgentPolicy) JSON() []byte { | ||
return p.raw | ||
} | ||
|
||
func NewAgentPolicyDumper(client *kibana.Client, agentPolicy string) *AgentPolicyDumper { | ||
return &AgentPolicyDumper{ | ||
name: agentPolicy, | ||
client: client, | ||
} | ||
} | ||
|
||
func NewAgentPoliciesDumper(client *kibana.Client) *AgentPoliciesDumper { | ||
return &AgentPoliciesDumper{ | ||
client: client, | ||
} | ||
} | ||
|
||
func (d *AgentPolicyDumper) getAgentPolicy(ctx context.Context) (*AgentPolicy, error) { | ||
if d.policy == nil { | ||
policy, err := d.client.GetRawPolicy(d.name) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
agentPolicy := AgentPolicy{name: d.name, raw: policy} | ||
d.policy = &agentPolicy | ||
} | ||
return d.policy, nil | ||
} | ||
|
||
func (d *AgentPolicyDumper) DumpAgentPolicy(ctx context.Context, dir string) error { | ||
agentPolicy, err := d.getAgentPolicy(ctx) | ||
if err != nil { | ||
return fmt.Errorf("failed to get agent policy: %w", err) | ||
} | ||
|
||
dir = filepath.Join(dir, AgentPoliciesDumpDir) | ||
err = dumpInstalledObject(dir, agentPolicy) | ||
if err != nil { | ||
return fmt.Errorf("failed to dump agent policy %s: %w", agentPolicy.Name(), err) | ||
} | ||
return nil | ||
} | ||
|
||
func (d *AgentPoliciesDumper) getAllAgentPolicies(ctx context.Context) ([]AgentPolicy, error) { | ||
if len(d.policies) == 0 { | ||
policies, err := d.client.ListRawPolicy() | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var policyName struct { | ||
ID string `json:"id"` | ||
} | ||
|
||
for _, policy := range policies { | ||
err = json.Unmarshal(policy, &policyName) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get Agent Policy ID: %w", err) | ||
} | ||
agentPolicy := AgentPolicy{name: policyName.ID, raw: policy} | ||
d.policies = append(d.policies, agentPolicy) | ||
} | ||
} | ||
return d.policies, nil | ||
} | ||
|
||
type packagePolicy struct { | ||
ID string `json:"id"` | ||
Name string `json:"name"` | ||
Package struct { | ||
Name string `json:"name"` | ||
Title string `json:"title"` | ||
Version string `json:"version"` | ||
} `json:"package"` | ||
} | ||
|
||
func getPackagesUsingAgentPolicy(packagePolicies []packagePolicy) []string { | ||
var packageNames []string | ||
for _, packagePolicy := range packagePolicies { | ||
packageNames = append(packageNames, packagePolicy.Package.Name) | ||
} | ||
return packageNames | ||
} | ||
|
||
func (d *AgentPoliciesDumper) getAgentPoliciesFilteredByPackage(ctx context.Context, packageName string) ([]AgentPolicy, error) { | ||
if len(d.policies) == 0 { | ||
policies, err := d.client.ListRawPolicy() | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var policyPackages struct { | ||
ID string `json:"id"` | ||
PackagePolicies []packagePolicy `json:"package_policies"` | ||
} | ||
|
||
for _, policy := range policies { | ||
err = json.Unmarshal(policy, &policyPackages) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get Agent Policy ID: %w", err) | ||
} | ||
packageNames := getPackagesUsingAgentPolicy(policyPackages.PackagePolicies) | ||
if !common.StringSliceContains(packageNames, packageName) { | ||
continue | ||
} | ||
jsoriano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
agentPolicy := AgentPolicy{name: policyPackages.ID, raw: policy} | ||
d.policies = append(d.policies, agentPolicy) | ||
} | ||
} | ||
return d.policies, nil | ||
} | ||
|
||
func (d *AgentPoliciesDumper) DumpAll(ctx context.Context, dir string) (count int, err error) { | ||
agentPolicies, err := d.getAllAgentPolicies(ctx) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to get agent policy: %w", err) | ||
} | ||
|
||
dir = filepath.Join(dir, AgentPoliciesDumpDir) | ||
for _, agentPolicy := range agentPolicies { | ||
err := dumpInstalledObject(dir, agentPolicy) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit. Maybe we should rename There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've just renamed it to dumpJSONResource. Something like internal/dump/utils.go ? I would move that function and formatJSON There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this could be moved to some common file, maybe called |
||
if err != nil { | ||
return 0, fmt.Errorf("failed to dump agent policy %s: %w", agentPolicy.Name(), err) | ||
} | ||
} | ||
return len(agentPolicies), nil | ||
} | ||
|
||
func (d *AgentPoliciesDumper) DumpAgentPoliciesFileteredByPackage(ctx context.Context, packageName, dir string) (count int, err error) { | ||
agentPolicies, err := d.getAgentPoliciesFilteredByPackage(ctx, packageName) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to get agent policy: %w", err) | ||
} | ||
|
||
dir = filepath.Join(dir, AgentPoliciesDumpDir) | ||
for _, agentPolicy := range agentPolicies { | ||
err := dumpInstalledObject(dir, agentPolicy) | ||
if err != nil { | ||
return 0, fmt.Errorf("failed to dump agent policy %s: %w", agentPolicy.Name(), err) | ||
} | ||
} | ||
return len(agentPolicies), nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,6 +72,50 @@ func (c *Client) GetPolicy(policyID string) (*Policy, error) { | |
return &resp.Item, nil | ||
} | ||
|
||
// GetRawPolicy fetches the given Policy with all the fields in the Ingest Manager. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, now the Ingest Manager is called the Fleet :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updating comments here. I was not totally sure about whether or not they were referring to the same. |
||
func (c *Client) GetRawPolicy(policyID string) (json.RawMessage, error) { | ||
statusCode, respBody, err := c.get(fmt.Sprintf("%s/agent_policies/%s", FleetAPI, policyID)) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "could not get policy") | ||
} | ||
|
||
if statusCode != http.StatusOK { | ||
return nil, fmt.Errorf("could not get policy; API status code = %d; response body = %s", statusCode, respBody) | ||
} | ||
|
||
var resp struct { | ||
Item json.RawMessage `json:"item"` | ||
} | ||
|
||
if err := json.Unmarshal(respBody, &resp); err != nil { | ||
return nil, errors.Wrap(err, "could not convert policy (response) to JSON") | ||
} | ||
|
||
return resp.Item, nil | ||
} | ||
|
||
// ListRawPolicy fetches all the Policies in the Ingest Manager. | ||
func (c *Client) ListRawPolicy() ([]json.RawMessage, error) { | ||
statusCode, respBody, err := c.get(fmt.Sprintf("%s/agent_policies?full=true", FleetAPI)) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "could not get policy") | ||
} | ||
mrodm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if statusCode != http.StatusOK { | ||
return nil, fmt.Errorf("could not get policy; API status code = %d; response body = %s", statusCode, respBody) | ||
} | ||
|
||
var resp struct { | ||
Items []json.RawMessage `json:"items"` | ||
} | ||
|
||
if err := json.Unmarshal(respBody, &resp); err != nil { | ||
return nil, errors.Wrap(err, "could not convert policy (response) to JSON") | ||
} | ||
|
||
return resp.Items, nil | ||
} | ||
|
||
// DeletePolicy removes the given Policy from the Ingest Manager. | ||
func (c *Client) DeletePolicy(p Policy) error { | ||
reqBody := `{ "agentPolicyId": "` + p.ID + `" }` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mention somewhere that if no flag is provided, all policies are dumped.