-
Notifications
You must be signed in to change notification settings - Fork 594
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(trustengine): Integrate Trust Engine into step config resolver (#…
…5032) * trust engine config and handelling for vault * add function for resolving trust engine reference * refactor * add basic test * adapt to new trust engine response format * remove accidental cyclic dependency * move trust engine hook config * refactor by separating code from vault * move trust engine files to own pkg * adapt to changes of previous commit * log full error response of trust engine API * enable getting multiple tokens from trustengine * remove comment * incorporate review comments * go generate * update unit tests * apply suggested changes from code review * fix unit tests * add unit tests for config pkg * make changes based on review comments * make trust engine token available in GeneralConfig and minor fixes * fix error logic when reading trust engine hook * make getResponse more flexible and update logging * update resource reference format * improve URL handling * improve logging * use errors.Wrap() instead of errors.Join() * update log messages based on suggestions * remove trustengine resource ref from Sonar step --------- Co-authored-by: Keshav <[email protected]> Co-authored-by: jliempt <>
- Loading branch information
1 parent
7e2604a
commit af5b738
Showing
6 changed files
with
393 additions
and
14 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
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,67 @@ | ||
package config | ||
|
||
import ( | ||
"errors" | ||
|
||
piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||
"github.com/SAP/jenkins-library/pkg/log" | ||
"github.com/SAP/jenkins-library/pkg/trustengine" | ||
) | ||
|
||
// const RefTypeTrustengineSecretFile = "trustengineSecretFile" | ||
const RefTypeTrustengineSecret = "trustengineSecret" | ||
|
||
// resolveAllTrustEngineReferences retrieves all the step's secrets from the Trust Engine | ||
func resolveAllTrustEngineReferences(config *StepConfig, params []StepParameters, trustEngineConfiguration trustengine.Configuration, client *piperhttp.Client) { | ||
for _, param := range params { | ||
if ref := param.GetReference(RefTypeTrustengineSecret); ref != nil { | ||
if config.Config[param.Name] == "" { | ||
log.Entry().Infof("Getting '%s' from Trust Engine", param.Name) | ||
token, err := trustengine.GetToken(ref.Default, client, trustEngineConfiguration) | ||
if err != nil { | ||
log.Entry().Info(" failed") | ||
log.Entry().WithError(err).Debugf("Couldn't get '%s' token from Trust Engine", ref.Default) | ||
continue | ||
} | ||
log.RegisterSecret(token) | ||
config.Config[param.Name] = token | ||
log.Entry().Info(" succeeded") | ||
} else { | ||
log.Entry().Debugf("Skipping retrieval of '%s' from Trust Engine: parameter already set", param.Name) | ||
} | ||
} | ||
} | ||
} | ||
|
||
// setTrustEngineConfiguration sets the server URL for the Trust Engine by taking it from the hooks | ||
func (c *Config) setTrustEngineConfiguration(hookConfig map[string]interface{}) error { | ||
trustEngineHook, ok := hookConfig["trustengine"].(map[string]interface{}) | ||
if !ok { | ||
return errors.New("no Trust Engine hook configuration found") | ||
} | ||
if serverURL, ok := trustEngineHook["serverURL"].(string); ok { | ||
c.trustEngineConfiguration.ServerURL = serverURL | ||
} else { | ||
return errors.New("no Trust Engine server URL found") | ||
} | ||
if tokenEndPoint, ok := trustEngineHook["tokenEndPoint"].(string); ok { | ||
c.trustEngineConfiguration.TokenEndPoint = tokenEndPoint | ||
} else { | ||
return errors.New("no Trust Engine service endpoint found") | ||
} | ||
if tokenQueryParamName, ok := trustEngineHook["tokenQueryParamName"].(string); ok { | ||
c.trustEngineConfiguration.TokenQueryParamName = tokenQueryParamName | ||
} else { | ||
return errors.New("no Trust Engine query parameter name found") | ||
} | ||
|
||
if len(c.trustEngineConfiguration.Token) == 0 { | ||
log.Entry().Debug("no Trust Engine token found") | ||
} | ||
return nil | ||
} | ||
|
||
// SetTrustEngineToken sets the token for the Trust Engine | ||
func (c *Config) SetTrustEngineToken(token string) { | ||
c.trustEngineConfiguration.Token = token | ||
} |
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,74 @@ | ||
//go:build unit | ||
// +build unit | ||
|
||
package config | ||
|
||
import ( | ||
"fmt" | ||
piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||
"github.com/SAP/jenkins-library/pkg/trustengine" | ||
"github.com/jarcoal/httpmock" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
const secretName = "sonar" | ||
const secretNameInTrustEngine = "sonarTrustengineSecretName" | ||
const testServerURL = "https://www.project-piper.io" | ||
const testTokenEndPoint = "tokens" | ||
const testTokenQueryParamName = "systems" | ||
const mockSonarToken = "mockSonarToken" | ||
|
||
var testFullURL = fmt.Sprintf("%s/%s?%s=", testServerURL, testTokenEndPoint, testTokenQueryParamName) | ||
var mockSingleTokenResponse = fmt.Sprintf("{\"sonar\": \"%s\"}", mockSonarToken) | ||
|
||
func TestTrustEngineConfig(t *testing.T) { | ||
httpmock.Activate() | ||
defer httpmock.DeactivateAndReset() | ||
httpmock.RegisterResponder(http.MethodGet, testFullURL+"sonar", httpmock.NewStringResponder(200, mockSingleTokenResponse)) | ||
|
||
stepParams := []StepParameters{createStepParam(secretName, RefTypeTrustengineSecret, secretNameInTrustEngine, secretName)} | ||
|
||
var trustEngineConfiguration = trustengine.Configuration{ | ||
Token: "testToken", | ||
ServerURL: testServerURL, | ||
TokenEndPoint: testTokenEndPoint, | ||
TokenQueryParamName: testTokenQueryParamName, | ||
} | ||
client := &piperhttp.Client{} | ||
client.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true}) | ||
|
||
t.Run("Load secret from Trust Engine - secret not set yet by Vault or config.yml", func(t *testing.T) { | ||
stepConfig := &StepConfig{Config: map[string]interface{}{ | ||
secretName: "", | ||
}} | ||
|
||
resolveAllTrustEngineReferences(stepConfig, stepParams, trustEngineConfiguration, client) | ||
assert.Equal(t, mockSonarToken, stepConfig.Config[secretName]) | ||
}) | ||
|
||
t.Run("Load secret from Trust Engine - secret already by Vault or config.yml", func(t *testing.T) { | ||
stepConfig := &StepConfig{Config: map[string]interface{}{ | ||
secretName: "aMockTokenFromVault", | ||
}} | ||
|
||
resolveAllTrustEngineReferences(stepConfig, stepParams, trustEngineConfiguration, client) | ||
assert.NotEqual(t, mockSonarToken, stepConfig.Config[secretName]) | ||
}) | ||
} | ||
|
||
func createStepParam(name, refType, trustengineSecretNameProperty, defaultSecretNameName string) StepParameters { | ||
return StepParameters{ | ||
Name: name, | ||
Aliases: []Alias{}, | ||
ResourceRef: []ResourceReference{ | ||
{ | ||
Type: refType, | ||
Name: trustengineSecretNameProperty, | ||
Default: defaultSecretNameName, | ||
}, | ||
}, | ||
} | ||
} |
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,135 @@ | ||
package trustengine | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
|
||
piperhttp "github.com/SAP/jenkins-library/pkg/http" | ||
"github.com/SAP/jenkins-library/pkg/log" | ||
) | ||
|
||
type Secret struct { | ||
Token string | ||
System string | ||
} | ||
|
||
type Response struct { | ||
Secrets []Secret | ||
} | ||
|
||
type Configuration struct { | ||
ServerURL string | ||
TokenEndPoint string | ||
TokenQueryParamName string | ||
Token string | ||
} | ||
|
||
// GetToken requests a single token | ||
func GetToken(refName string, client *piperhttp.Client, trustEngineConfiguration Configuration) (string, error) { | ||
secrets, err := GetSecrets([]string{refName}, client, trustEngineConfiguration) | ||
if err != nil { | ||
return "", errors.Wrap(err, "couldn't get token from trust engine") | ||
} | ||
for _, s := range secrets { | ||
if s.System == refName { | ||
return s.Token, nil | ||
} | ||
} | ||
return "", errors.New("could not find token in trust engine response") | ||
} | ||
|
||
// GetSecrets transforms the trust engine JSON response into trust engine secrets, and can be used to request multiple tokens | ||
func GetSecrets(refNames []string, client *piperhttp.Client, trustEngineConfiguration Configuration) ([]Secret, error) { | ||
var secrets []Secret | ||
query := url.Values{ | ||
trustEngineConfiguration.TokenQueryParamName: { | ||
strings.Join(refNames, ","), | ||
}, | ||
} | ||
response, err := getResponse(trustEngineConfiguration.ServerURL, trustEngineConfiguration.TokenEndPoint, query, client) | ||
if err != nil { | ||
return secrets, errors.Wrap(err, "getting secrets from trust engine failed") | ||
} | ||
for k, v := range response { | ||
secrets = append(secrets, Secret{ | ||
System: k, | ||
Token: v}) | ||
} | ||
|
||
return secrets, nil | ||
} | ||
|
||
// getResponse returns a map of the JSON response that the trust engine puts out | ||
func getResponse(serverURL, endpoint string, query url.Values, client *piperhttp.Client) (map[string]string, error) { | ||
var secrets map[string]string | ||
|
||
rawURL, err := parseURL(serverURL, endpoint, query) | ||
if err != nil { | ||
return secrets, errors.Wrap(err, "parsing trust engine url failed") | ||
} | ||
header := make(http.Header) | ||
header.Add("Accept", "application/json") | ||
|
||
log.Entry().Debugf(" with URL %s", rawURL) | ||
response, err := client.SendRequest(http.MethodGet, rawURL, nil, header, nil) | ||
if err != nil { | ||
if response != nil { | ||
// the body contains full error message which we want to log | ||
defer response.Body.Close() | ||
bodyBytes, bodyErr := io.ReadAll(response.Body) | ||
if bodyErr == nil { | ||
err = errors.Wrap(err, string(bodyBytes)) | ||
} | ||
} | ||
return secrets, errors.Wrap(err, "getting response from trust engine failed") | ||
} | ||
defer response.Body.Close() | ||
|
||
err = json.NewDecoder(response.Body).Decode(&secrets) | ||
if err != nil { | ||
return secrets, errors.Wrap(err, "getting response from trust engine failed") | ||
} | ||
|
||
return secrets, nil | ||
} | ||
|
||
// parseURL creates the full URL for a Trust Engine GET request | ||
func parseURL(serverURL, endpoint string, query url.Values) (string, error) { | ||
rawFullEndpoint, err := url.JoinPath(serverURL, endpoint) | ||
if err != nil { | ||
return "", errors.New("error parsing trust engine URL") | ||
} | ||
fullURL, err := url.Parse(rawFullEndpoint) | ||
if err != nil { | ||
return "", errors.New("error parsing trust engine URL") | ||
} | ||
// commas and spaces shouldn't be escaped since the Trust Engine won't accept it | ||
unescapedRawQuery, err := url.QueryUnescape(query.Encode()) | ||
if err != nil { | ||
return "", errors.New("error parsing trust engine URL") | ||
} | ||
fullURL.RawQuery = unescapedRawQuery | ||
return fullURL.String(), nil | ||
} | ||
|
||
// PrepareClient adds the Trust Engine authentication token to the client | ||
func PrepareClient(client *piperhttp.Client, trustEngineConfiguration Configuration) *piperhttp.Client { | ||
var logEntry *logrus.Entry | ||
if logrus.GetLevel() < logrus.DebugLevel { | ||
logger := logrus.New() | ||
logger.SetOutput(io.Discard) | ||
logEntry = logrus.NewEntry(logger) | ||
} | ||
client.SetOptions(piperhttp.ClientOptions{ | ||
Token: fmt.Sprintf("Bearer %s", trustEngineConfiguration.Token), | ||
Logger: logEntry, | ||
}) | ||
return client | ||
} |
Oops, something went wrong.