mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-30 05:59:39 +02:00
feat(vault): Facilitate Vault OIDC token (#4916)
* add functionality to retrieve Vault OIDC token * fix tests for now * update error Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com> * implement commented tests * run mockery for config pkg --------- Co-authored-by: jliempt <> Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
This commit is contained in:
parent
6c4a860bd5
commit
f5fbb7e9d9
@ -13,3 +13,8 @@ packages:
|
||||
dir: pkg/influx/mocks
|
||||
interfaces:
|
||||
WriteAPIBlocking:
|
||||
github.com/SAP/jenkins-library/pkg/config:
|
||||
config:
|
||||
dir: pkg/config/mocks
|
||||
interfaces:
|
||||
VaultClient:
|
||||
|
@ -54,6 +54,7 @@ type HookConfiguration struct {
|
||||
SentryConfig SentryConfiguration `json:"sentry,omitempty"`
|
||||
SplunkConfig SplunkConfiguration `json:"splunk,omitempty"`
|
||||
PendoConfig PendoConfiguration `json:"pendo,omitempty"`
|
||||
OIDCConfig OIDCConfiguration `json:"oidc,omitempty"`
|
||||
}
|
||||
|
||||
// SentryConfiguration defines the configuration options for the Sentry logging system
|
||||
@ -76,6 +77,11 @@ type PendoConfiguration struct {
|
||||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
// OIDCConfiguration defines the configuration options for the OpenID Connect authentication system
|
||||
type OIDCConfiguration struct {
|
||||
RoleID string `json:",roleID,omitempty"`
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "piper",
|
||||
Short: "Executes CI/CD steps from project 'Piper' ",
|
||||
|
@ -257,7 +257,7 @@ func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON stri
|
||||
// check whether vault should be skipped
|
||||
if skip, ok := stepConfig.Config["skipVault"].(bool); !ok || !skip {
|
||||
// fetch secrets from vault
|
||||
vaultClient, err := getVaultClientFromConfig(stepConfig, c.vaultCredentials)
|
||||
vaultClient, err := GetVaultClientFromConfig(stepConfig.Config, c.vaultCredentials)
|
||||
if err != nil {
|
||||
return StepConfig{}, err
|
||||
}
|
||||
|
@ -1,19 +1,35 @@
|
||||
// Code generated by mockery v2.3.0. DO NOT EDIT.
|
||||
// Code generated by mockery v2.42.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
// VaultMock is an autogenerated mock type for the vaultClient type
|
||||
type VaultMock struct {
|
||||
// VaultClient is an autogenerated mock type for the VaultClient type
|
||||
type VaultClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type VaultClient_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *VaultClient) EXPECT() *VaultClient_Expecter {
|
||||
return &VaultClient_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetKvSecret provides a mock function with given fields: _a0
|
||||
func (_m *VaultMock) GetKvSecret(_a0 string) (map[string]string, error) {
|
||||
func (_m *VaultClient) GetKvSecret(_a0 string) (map[string]string, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetKvSecret")
|
||||
}
|
||||
|
||||
var r0 map[string]string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (map[string]string, error)); ok {
|
||||
return rf(_a0)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) map[string]string); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
@ -22,7 +38,6 @@ func (_m *VaultMock) GetKvSecret(_a0 string) (map[string]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
@ -32,7 +47,132 @@ func (_m *VaultMock) GetKvSecret(_a0 string) (map[string]string, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// VaultClient_GetKvSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetKvSecret'
|
||||
type VaultClient_GetKvSecret_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetKvSecret is a helper method to define mock.On call
|
||||
// - _a0 string
|
||||
func (_e *VaultClient_Expecter) GetKvSecret(_a0 interface{}) *VaultClient_GetKvSecret_Call {
|
||||
return &VaultClient_GetKvSecret_Call{Call: _e.mock.On("GetKvSecret", _a0)}
|
||||
}
|
||||
|
||||
func (_c *VaultClient_GetKvSecret_Call) Run(run func(_a0 string)) *VaultClient_GetKvSecret_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *VaultClient_GetKvSecret_Call) Return(_a0 map[string]string, _a1 error) *VaultClient_GetKvSecret_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *VaultClient_GetKvSecret_Call) RunAndReturn(run func(string) (map[string]string, error)) *VaultClient_GetKvSecret_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetOIDCTokenByValidation provides a mock function with given fields: _a0
|
||||
func (_m *VaultClient) GetOIDCTokenByValidation(_a0 string) (string, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetOIDCTokenByValidation")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
|
||||
return rf(_a0)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// VaultClient_GetOIDCTokenByValidation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOIDCTokenByValidation'
|
||||
type VaultClient_GetOIDCTokenByValidation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetOIDCTokenByValidation is a helper method to define mock.On call
|
||||
// - _a0 string
|
||||
func (_e *VaultClient_Expecter) GetOIDCTokenByValidation(_a0 interface{}) *VaultClient_GetOIDCTokenByValidation_Call {
|
||||
return &VaultClient_GetOIDCTokenByValidation_Call{Call: _e.mock.On("GetOIDCTokenByValidation", _a0)}
|
||||
}
|
||||
|
||||
func (_c *VaultClient_GetOIDCTokenByValidation_Call) Run(run func(_a0 string)) *VaultClient_GetOIDCTokenByValidation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *VaultClient_GetOIDCTokenByValidation_Call) Return(_a0 string, _a1 error) *VaultClient_GetOIDCTokenByValidation_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *VaultClient_GetOIDCTokenByValidation_Call) RunAndReturn(run func(string) (string, error)) *VaultClient_GetOIDCTokenByValidation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// MustRevokeToken provides a mock function with given fields:
|
||||
func (_m *VaultMock) MustRevokeToken() {
|
||||
func (_m *VaultClient) MustRevokeToken() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// VaultClient_MustRevokeToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MustRevokeToken'
|
||||
type VaultClient_MustRevokeToken_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// MustRevokeToken is a helper method to define mock.On call
|
||||
func (_e *VaultClient_Expecter) MustRevokeToken() *VaultClient_MustRevokeToken_Call {
|
||||
return &VaultClient_MustRevokeToken_Call{Call: _e.mock.On("MustRevokeToken")}
|
||||
}
|
||||
|
||||
func (_c *VaultClient_MustRevokeToken_Call) Run(run func()) *VaultClient_MustRevokeToken_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *VaultClient_MustRevokeToken_Call) Return() *VaultClient_MustRevokeToken_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *VaultClient_MustRevokeToken_Call) RunAndReturn(run func()) *VaultClient_MustRevokeToken_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewVaultClient creates a new instance of VaultClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewVaultClient(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *VaultClient {
|
||||
mock := &VaultClient{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
@ -75,10 +75,11 @@ type VaultCredentials struct {
|
||||
VaultToken string
|
||||
}
|
||||
|
||||
// vaultClient interface for mocking
|
||||
type vaultClient interface {
|
||||
// VaultClient interface for mocking
|
||||
type VaultClient interface {
|
||||
GetKvSecret(string) (map[string]string, error)
|
||||
MustRevokeToken()
|
||||
GetOIDCTokenByValidation(string) (string, error)
|
||||
}
|
||||
|
||||
func (s *StepConfig) mixinVaultConfig(parameters []StepParameters, configs ...map[string]interface{}) {
|
||||
@ -91,8 +92,8 @@ func (s *StepConfig) mixinVaultConfig(parameters []StepParameters, configs ...ma
|
||||
}
|
||||
}
|
||||
|
||||
func getVaultClientFromConfig(config StepConfig, creds VaultCredentials) (vaultClient, error) {
|
||||
address, addressOk := config.Config["vaultServerUrl"].(string)
|
||||
func GetVaultClientFromConfig(config map[string]interface{}, creds VaultCredentials) (VaultClient, error) {
|
||||
address, addressOk := config["vaultServerUrl"].(string)
|
||||
// if vault isn't used it's not an error
|
||||
if !addressOk || creds.VaultToken == "" && (creds.AppRoleID == "" || creds.AppRoleSecretID == "") {
|
||||
log.Entry().Debug("Vault not configured")
|
||||
@ -102,11 +103,11 @@ func getVaultClientFromConfig(config StepConfig, creds VaultCredentials) (vaultC
|
||||
log.Entry().Debugf(" with URL %s", address)
|
||||
namespace := ""
|
||||
// namespaces are only available in vault enterprise so using them should be optional
|
||||
if config.Config["vaultNamespace"] != nil {
|
||||
namespace = config.Config["vaultNamespace"].(string)
|
||||
if config["vaultNamespace"] != nil {
|
||||
namespace = config["vaultNamespace"].(string)
|
||||
log.Entry().Debugf(" with namespace %s", namespace)
|
||||
}
|
||||
var client vaultClient
|
||||
var client VaultClient
|
||||
var err error
|
||||
clientConfig := &vault.Config{Config: &api.Config{Address: address}, Namespace: namespace}
|
||||
if creds.VaultToken != "" {
|
||||
@ -124,7 +125,7 @@ func getVaultClientFromConfig(config StepConfig, creds VaultCredentials) (vaultC
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func resolveAllVaultReferences(config *StepConfig, client vaultClient, params []StepParameters) {
|
||||
func resolveAllVaultReferences(config *StepConfig, client VaultClient, params []StepParameters) {
|
||||
for _, param := range params {
|
||||
if ref := param.GetReference("vaultSecret"); ref != nil {
|
||||
resolveVaultReference(ref, config, client, param)
|
||||
@ -135,7 +136,7 @@ func resolveAllVaultReferences(config *StepConfig, client vaultClient, params []
|
||||
}
|
||||
}
|
||||
|
||||
func resolveVaultReference(ref *ResourceReference, config *StepConfig, client vaultClient, param StepParameters) {
|
||||
func resolveVaultReference(ref *ResourceReference, config *StepConfig, client VaultClient, param StepParameters) {
|
||||
vaultDisableOverwrite, _ := config.Config["vaultDisableOverwrite"].(bool)
|
||||
if _, ok := config.Config[param.Name].(string); vaultDisableOverwrite && ok {
|
||||
log.Entry().Debugf("Not fetching '%s' from Vault since it has already been set", param.Name)
|
||||
@ -173,20 +174,20 @@ func resolveVaultReference(ref *ResourceReference, config *StepConfig, client va
|
||||
}
|
||||
}
|
||||
|
||||
func resolveVaultTestCredentialsWrapper(config *StepConfig, client vaultClient) {
|
||||
func resolveVaultTestCredentialsWrapper(config *StepConfig, client VaultClient) {
|
||||
log.Entry().Infof("Resolving test credentials wrapper")
|
||||
resolveVaultCredentialsWrapperBase(config, client, vaultTestCredentialPath, vaultTestCredentialKeys, vaultTestCredentialEnvPrefix, resolveVaultTestCredentials)
|
||||
}
|
||||
|
||||
func resolveVaultCredentialsWrapper(config *StepConfig, client vaultClient) {
|
||||
func resolveVaultCredentialsWrapper(config *StepConfig, client VaultClient) {
|
||||
log.Entry().Infof("Resolving credentials wrapper")
|
||||
resolveVaultCredentialsWrapperBase(config, client, vaultCredentialPath, vaultCredentialKeys, vaultCredentialEnvPrefix, resolveVaultCredentials)
|
||||
}
|
||||
|
||||
func resolveVaultCredentialsWrapperBase(
|
||||
config *StepConfig, client vaultClient,
|
||||
config *StepConfig, client VaultClient,
|
||||
vaultCredPath, vaultCredKeys, vaultCredEnvPrefix string,
|
||||
resolveVaultCredentials func(config *StepConfig, client vaultClient),
|
||||
resolveVaultCredentials func(config *StepConfig, client VaultClient),
|
||||
) {
|
||||
switch config.Config[vaultCredPath].(type) {
|
||||
case string:
|
||||
@ -230,7 +231,7 @@ func resolveVaultCredentialsWrapperBase(
|
||||
}
|
||||
|
||||
// resolve test credential keys and expose as environment variables
|
||||
func resolveVaultTestCredentials(config *StepConfig, client vaultClient) {
|
||||
func resolveVaultTestCredentials(config *StepConfig, client VaultClient) {
|
||||
credPath, pathOk := config.Config[vaultTestCredentialPath].(string)
|
||||
keys := getTestCredentialKeys(config)
|
||||
if !(pathOk && keys != nil) || credPath == "" || len(keys) == 0 {
|
||||
@ -267,7 +268,7 @@ func resolveVaultTestCredentials(config *StepConfig, client vaultClient) {
|
||||
}
|
||||
}
|
||||
|
||||
func resolveVaultCredentials(config *StepConfig, client vaultClient) {
|
||||
func resolveVaultCredentials(config *StepConfig, client VaultClient) {
|
||||
credPath, pathOk := config.Config[vaultCredentialPath].(string)
|
||||
keys := getCredentialKeys(config)
|
||||
if !(pathOk && keys != nil) || credPath == "" || len(keys) == 0 {
|
||||
@ -449,7 +450,7 @@ func createTemporarySecretFile(namePattern string, content string) (string, erro
|
||||
return file.Name(), nil
|
||||
}
|
||||
|
||||
func lookupPath(client vaultClient, path string, param *StepParameters) *string {
|
||||
func lookupPath(client VaultClient, path string, param *StepParameters) *string {
|
||||
log.Entry().Debugf(" with Vault path '%s'", path)
|
||||
secret, err := client.GetKvSecret(path)
|
||||
if err != nil {
|
||||
|
@ -22,7 +22,7 @@ func TestVaultConfigLoad(t *testing.T) {
|
||||
const secretNameOverrideKey = "mySecretVaultSecretName"
|
||||
t.Parallel()
|
||||
t.Run("Load secret from vault", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
}}
|
||||
@ -35,7 +35,7 @@ func TestVaultConfigLoad(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Load secret from Vault with path override", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
secretNameOverrideKey: "overrideSecretName",
|
||||
@ -49,7 +49,7 @@ func TestVaultConfigLoad(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Secrets are not overwritten", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
secretName: "preset value",
|
||||
@ -64,7 +64,7 @@ func TestVaultConfigLoad(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Secrets can be overwritten", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
secretName: "preset value",
|
||||
@ -78,7 +78,7 @@ func TestVaultConfigLoad(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Error is passed through", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
}}
|
||||
@ -89,7 +89,7 @@ func TestVaultConfigLoad(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Secret doesn't exist", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
}}
|
||||
@ -101,7 +101,7 @@ func TestVaultConfigLoad(t *testing.T) {
|
||||
|
||||
t.Run("Alias names should be considered", func(t *testing.T) {
|
||||
aliasName := "alias"
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
}}
|
||||
@ -115,7 +115,7 @@ func TestVaultConfigLoad(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Search over multiple paths", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultBasePath": "team2",
|
||||
"vaultPath": "team1",
|
||||
@ -131,7 +131,7 @@ func TestVaultConfigLoad(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("No BasePath is stepConfig.Configured", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{}}
|
||||
stepParams := []StepParameters{stepParam(secretName, "vaultSecret", secretNameOverrideKey, secretName)}
|
||||
resolveAllVaultReferences(&stepConfig, vaultMock, stepParams)
|
||||
@ -144,7 +144,7 @@ func TestVaultSecretFiles(t *testing.T) {
|
||||
const secretName = "testSecret"
|
||||
const secretNameOverrideKey = "mySecretVaultSecretName"
|
||||
t.Run("Test Vault Secret File Reference", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
}}
|
||||
@ -164,7 +164,7 @@ func TestVaultSecretFiles(t *testing.T) {
|
||||
VaultSecretFileDirectory = ""
|
||||
|
||||
t.Run("Test temporary secret file cleanup", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
}}
|
||||
@ -232,7 +232,7 @@ func TestResolveVaultTestCredentialsWrapper(t *testing.T) {
|
||||
t.Run("Default test credential prefix", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// init
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
envPrefix := "PIPER_TESTCREDENTIAL_"
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
@ -272,7 +272,7 @@ func TestResolveVaultTestCredentialsWrapper(t *testing.T) {
|
||||
t.Run("Multiple test credential prefixes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// init
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
envPrefixes := []interface{}{"TEST1_", "TEST2_"}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
@ -313,7 +313,7 @@ func TestResolveVaultTestCredentialsWrapper(t *testing.T) {
|
||||
t.Run("Multiple custom general purpuse credential environment prefixes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// init
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
envPrefixes := []interface{}{"CUSTOM1_", "CUSTOM2_"}
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
@ -362,7 +362,7 @@ func TestResolveVaultTestCredentialsWrapper(t *testing.T) {
|
||||
t.Run("Custom general purpose credential prefix along with fixed standard prefix", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// init
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
standardEnvPrefix := "PIPER_VAULTCREDENTIAL_"
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
@ -401,7 +401,7 @@ func TestResolveVaultTestCredentials(t *testing.T) {
|
||||
t.Run("Default test credential prefix", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// init
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
envPrefix := "PIPER_TESTCREDENTIAL_"
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
@ -438,7 +438,7 @@ func TestResolveVaultTestCredentials(t *testing.T) {
|
||||
t.Run("Custom general purpose credential prefix along with fixed standard prefix", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// init
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
standardEnvPrefix := "PIPER_VAULTCREDENTIAL_"
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
@ -474,7 +474,7 @@ func TestResolveVaultTestCredentials(t *testing.T) {
|
||||
t.Run("Custom test credential prefix", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// init
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
vaultMock := &mocks.VaultClient{}
|
||||
envPrefix := "CUSTOM_CREDENTIAL_"
|
||||
stepConfig := StepConfig{Config: map[string]interface{}{
|
||||
"vaultPath": "team1",
|
||||
|
@ -34,6 +34,12 @@ type logicalClient interface {
|
||||
Write(string, map[string]interface{}) (*api.Secret, error)
|
||||
}
|
||||
|
||||
type VaultCredentials struct {
|
||||
AppRoleID string
|
||||
AppRoleSecretID string
|
||||
VaultToken string
|
||||
}
|
||||
|
||||
// NewClient instantiates a Client and sets the specified token
|
||||
func NewClient(config *Config, token string) (Client, error) {
|
||||
if config == nil {
|
||||
|
84
pkg/vault/oidc.go
Normal file
84
pkg/vault/oidc.go
Normal file
@ -0,0 +1,84 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type JwtPayload struct {
|
||||
Expire int64 `json:"exp"`
|
||||
}
|
||||
|
||||
// getOIDCToken returns the generated OIDC token and sets it in the env
|
||||
func (v Client) getOIDCToken(roleID string) (string, error) {
|
||||
oidcPath := sanitizePath(path.Join("identity/oidc/token/", roleID))
|
||||
c := v.lClient
|
||||
jwt, err := c.Read(oidcPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
token := jwt.Data["token"].(string)
|
||||
log.RegisterSecret(token)
|
||||
os.Setenv("PIPER_OIDCIdentityToken", token)
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// getJWTTokenPayload returns the payload of the JWT token using base64 decoding
|
||||
func getJWTTokenPayload(token string) ([]byte, error) {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) >= 2 {
|
||||
substr := parts[1]
|
||||
decodedBytes, err := base64.RawStdEncoding.DecodeString(substr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "JWT payload couldn't be decoded: %s")
|
||||
}
|
||||
return decodedBytes, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Not a valid JWT token")
|
||||
}
|
||||
|
||||
func oidcTokenIsValid(token string) bool {
|
||||
payload, err := getJWTTokenPayload(token)
|
||||
if err != nil {
|
||||
log.Entry().Debugf("OIDC token couldn't be validated: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
var jwtPayload JwtPayload
|
||||
err = json.Unmarshal(payload, &jwtPayload)
|
||||
if err != nil {
|
||||
log.Entry().Debugf("OIDC token couldn't be validated: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
expiryTime := time.Unix(jwtPayload.Expire, 0)
|
||||
currentTime := time.Now()
|
||||
|
||||
return expiryTime.After(currentTime)
|
||||
}
|
||||
|
||||
// GetOIDCTokenByValidation returns the token if token is expired then get a new token else return old token
|
||||
func (v Client) GetOIDCTokenByValidation(roleID string) (string, error) {
|
||||
token := os.Getenv("PIPER_OIDCIdentityToken")
|
||||
if token != "" && oidcTokenIsValid(token) {
|
||||
return token, nil
|
||||
}
|
||||
|
||||
token, err := v.getOIDCToken(roleID)
|
||||
if token == "" || err != nil {
|
||||
return "", errors.Wrap(err, "failed to get OIDC token")
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
87
pkg/vault/oidc_test.go
Normal file
87
pkg/vault/oidc_test.go
Normal file
@ -0,0 +1,87 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/vault/mocks"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOIDC(t *testing.T) {
|
||||
oidcPath := "identity/oidc/token/testRoleID"
|
||||
mockPayload := base64.RawStdEncoding.EncodeToString([]byte("testOIDCtoken123"))
|
||||
mockToken := fmt.Sprintf("hvs.%s", mockPayload)
|
||||
|
||||
mockJwt := &api.Secret{
|
||||
Data: map[string]interface{}{
|
||||
"path": oidcPath,
|
||||
"token": mockToken,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Test getting OIDC token - token non-existent in env yet", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// init
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", oidcPath).Return(mockJwt, nil)
|
||||
|
||||
// run
|
||||
token, err := client.GetOIDCTokenByValidation("testRoleID")
|
||||
|
||||
// assert
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, token, mockToken)
|
||||
})
|
||||
|
||||
t.Run("Test getting OIDC token - token exists in env and is valid", func(t *testing.T) {
|
||||
// init
|
||||
// still valid for 10 minutes
|
||||
expiryTime := time.Now().Local().Add(time.Minute * time.Duration(10))
|
||||
payload := fmt.Sprintf("{\"exp\": %d}", expiryTime.Unix())
|
||||
payloadB64 := base64.RawStdEncoding.EncodeToString([]byte(payload))
|
||||
token := fmt.Sprintf("hvs.%s", payloadB64)
|
||||
|
||||
t.Setenv("PIPER_OIDCIdentityToken", token)
|
||||
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", oidcPath).Return(mockJwt, nil)
|
||||
|
||||
// run
|
||||
tokenResult, err := client.GetOIDCTokenByValidation("testRoleID")
|
||||
|
||||
// assert
|
||||
assert.Equal(t, token, tokenResult)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Test getting OIDC token - token exists in env and is invalid", func(t *testing.T) {
|
||||
//init
|
||||
// expired 10 minutes ago (time is subtracted!)
|
||||
expiryTime := time.Now().Add(-time.Minute * time.Duration(10))
|
||||
payload := fmt.Sprintf("{\"exp\": %d}", expiryTime.Unix())
|
||||
payloadB64 := base64.RawStdEncoding.EncodeToString([]byte(payload))
|
||||
token := fmt.Sprintf("hvs.%s", payloadB64)
|
||||
|
||||
t.Setenv("PIPER_OIDCIdentityToken", token)
|
||||
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", oidcPath).Return(mockJwt, nil)
|
||||
|
||||
// run
|
||||
tokenResult, err := client.GetOIDCTokenByValidation("testRoleID")
|
||||
|
||||
// assert
|
||||
client.GetOIDCTokenByValidation("testRoleID")
|
||||
assert.Equal(t, mockToken, tokenResult)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user