-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* replace secret refs * fix check, remove TODO * moved ReadSecrets to bulker, tests * fix lint, more tests * added nil check * added changelog * fix nil check * added comments * added integration test on secrets * Update internal/pkg/policy/secret.go Co-authored-by: Josh Dover <[email protected]> * Update internal/pkg/policy/secret.go Co-authored-by: Josh Dover <[email protected]> * rename to getSecretValues, moved out regex * refactored getPolicyInputsWithSecrets * added kibana_system to kibana.yml * create kibana user * fixed integration test --------- Co-authored-by: Josh Dover <[email protected]>
- Loading branch information
1 parent
4834215
commit 32b7910
Showing
14 changed files
with
651 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Kind can be one of: | ||
# - breaking-change: a change to previously-documented behavior | ||
# - deprecation: functionality that is being removed in a later release | ||
# - bug-fix: fixes a problem in a previous version | ||
# - enhancement: extends functionality but does not break or fix existing behavior | ||
# - feature: new functionality | ||
# - known-issue: problems that we are aware of in a given version | ||
# - security: impacts on the security of a product or a user’s deployment. | ||
# - upgrade: important information for someone upgrading from a prior version | ||
# - other: does not fit into any of the other categories | ||
kind: feature | ||
|
||
# Change summary; a 80ish characters long description of the change. | ||
summary: Replacing secret references with secret values | ||
|
||
# Long description; in case the summary is not enough to describe the change | ||
# this field accommodate a description without length limits. | ||
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. | ||
description: Secrets are being stored in a separate index in elasticsearch, and secret references are included in an agent policy. | ||
Secret references are replaced with secret values in the agent policy before sending it out to agents on checkin. | ||
|
||
# Affected component; a word indicating the component this changeset affects. | ||
component: | ||
|
||
# PR URL; optional; the PR number that added the changeset. | ||
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. | ||
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. | ||
# Please provide it if you are adding a fragment for a different PR. | ||
pr: 2863 | ||
|
||
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). | ||
# If not present is automatically filled by the tooling with the issue linked to the PR number. | ||
issue: 2485 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// 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 bulk | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/elastic/go-elasticsearch/v8" | ||
) | ||
|
||
type ExtendedClient struct { | ||
*elasticsearch.Client | ||
Custom *ExtendedAPI | ||
} | ||
|
||
type ExtendedAPI struct { | ||
*elasticsearch.Client | ||
} | ||
|
||
// Read secret values with custom ES API added in Fleet ES plugin, there is no direct access to secrets index | ||
// GET /_fleet/secret/secretId | ||
func (c *ExtendedAPI) Read(ctx context.Context, secretID string) (*SecretResponse, error) { | ||
req, err := http.NewRequestWithContext(ctx, "GET", "/_fleet/secret/"+secretID, nil) | ||
req.Header.Set("Content-Type", "application/json") | ||
req.Header.Set("Accept", "application/json") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
res, err := c.Perform(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer res.Body.Close() | ||
var secretResp SecretResponse | ||
|
||
err = json.NewDecoder(res.Body).Decode(&secretResp) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &secretResp, nil | ||
} | ||
|
||
type SecretResponse struct { | ||
Value string | ||
} | ||
|
||
func ReadSecret(ctx context.Context, client *elasticsearch.Client, secretID string) (string, error) { | ||
es := ExtendedClient{Client: client, Custom: &ExtendedAPI{client}} | ||
res, err := es.Custom.Read(ctx, secretID) | ||
if err != nil { | ||
return "", err | ||
} | ||
if res == nil { | ||
return "", nil | ||
} | ||
return (*res).Value, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// 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 policy | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/elastic/fleet-server/v7/internal/pkg/bulk" | ||
) | ||
|
||
type SecretReference struct { | ||
ID string `json:"id"` | ||
} | ||
|
||
var ( | ||
secretRegex = regexp.MustCompile(`\$co\.elastic\.secret{(.*)}`) | ||
) | ||
|
||
// read secret values that belong to the agent policy's secret references, returns secrets as id:value map | ||
func getSecretValues(ctx context.Context, secretRefsRaw json.RawMessage, bulker bulk.Bulk) (map[string]string, error) { | ||
if secretRefsRaw == nil { | ||
return nil, nil | ||
} | ||
|
||
var secretValues []SecretReference | ||
err := json.Unmarshal([]byte(secretRefsRaw), &secretValues) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
ids := make([]string, 0) | ||
for _, ref := range secretValues { | ||
ids = append(ids, ref.ID) | ||
} | ||
|
||
results, err := bulker.ReadSecrets(ctx, ids) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return results, nil | ||
} | ||
|
||
// read inputs and secret_references from agent policy | ||
// replace values of secret refs in inputs and input streams properties | ||
func getPolicyInputsWithSecrets(ctx context.Context, fields map[string]json.RawMessage, bulker bulk.Bulk) ([]map[string]interface{}, error) { | ||
if fields["inputs"] == nil { | ||
return nil, nil | ||
} | ||
|
||
var inputs []map[string]interface{} | ||
err := json.Unmarshal([]byte(fields["inputs"]), &inputs) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if fields["secret_references"] == nil { | ||
return inputs, nil | ||
} | ||
|
||
secretValues, err := getSecretValues(ctx, fields["secret_references"], bulker) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
result := make([]map[string]interface{}, 0) | ||
for _, input := range inputs { | ||
newInput := make(map[string]interface{}) | ||
for k, v := range input { | ||
// replace secret refs in input stream fields | ||
if k == "streams" { | ||
if streams, ok := input[k].([]any); ok { | ||
newInput[k] = processStreams(streams, secretValues) | ||
} | ||
// replace secret refs in input fields | ||
} else if ref, ok := input[k].(string); ok { | ||
val := replaceSecretRef(ref, secretValues) | ||
newInput[k] = val | ||
} | ||
// if any field was not processed, add back as is | ||
if _, ok := newInput[k]; !ok { | ||
newInput[k] = v | ||
} | ||
} | ||
result = append(result, newInput) | ||
} | ||
return result, nil | ||
} | ||
|
||
func processStreams(streams []any, secretValues map[string]string) []any { | ||
newStreams := make([]any, 0) | ||
for _, stream := range streams { | ||
if streamMap, ok := stream.(map[string]interface{}); ok { | ||
newStream := replaceSecretsInStream(streamMap, secretValues) | ||
newStreams = append(newStreams, newStream) | ||
} else { | ||
newStreams = append(newStreams, stream) | ||
} | ||
} | ||
return newStreams | ||
} | ||
|
||
// if field values are secret refs, replace with secret value, otherwise noop | ||
func replaceSecretsInStream(streamMap map[string]interface{}, secretValues map[string]string) map[string]interface{} { | ||
newStream := make(map[string]interface{}) | ||
for streamKey, streamVal := range streamMap { | ||
if streamRef, ok := streamMap[streamKey].(string); ok { | ||
replacedVal := replaceSecretRef(streamRef, secretValues) | ||
newStream[streamKey] = replacedVal | ||
} else { | ||
newStream[streamKey] = streamVal | ||
} | ||
} | ||
return newStream | ||
} | ||
|
||
// replace values mathing a secret ref regex, e.g. $co.elastic.secret{<secret ref>} -> <secret value> | ||
func replaceSecretRef(ref string, secretValues map[string]string) string { | ||
matches := secretRegex.FindStringSubmatch(ref) | ||
if len(matches) > 1 { | ||
secretRef := matches[1] | ||
if val, ok := secretValues[secretRef]; ok { | ||
return strings.Replace(ref, matches[0], val, 1) | ||
} | ||
} | ||
return ref | ||
} |
Oops, something went wrong.