1
0
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:
Jordi van Liempt 2024-05-07 14:19:39 +02:00 committed by GitHub
parent 6c4a860bd5
commit f5fbb7e9d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 370 additions and 41 deletions

View File

@ -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:

View File

@ -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' ",

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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",

View File

@ -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
View 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
View 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)
})
}