mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Add support for Yandex Lockbox: long lived connections to lockbox api
This commit is contained in:
parent
c7229199f3
commit
1e66d123b9
6 changed files with 581 additions and 128 deletions
1
go.mod
1
go.mod
|
@ -74,6 +74,7 @@ require (
|
|||
golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4 // indirect
|
||||
google.golang.org/api v0.30.0
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a
|
||||
google.golang.org/grpc v1.31.0
|
||||
honnef.co/go/tools v0.1.4 // indirect
|
||||
k8s.io/api v0.21.2
|
||||
k8s.io/apimachinery v0.21.2
|
||||
|
|
|
@ -15,18 +15,25 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
)
|
||||
|
||||
// Creates LockboxClient with the given authorized key.
|
||||
type LockboxClientCreator interface {
|
||||
Create(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (LockboxClient, error)
|
||||
// Creates Lockbox clients and Yandex.Cloud IAM tokens.
|
||||
type YandexCloudCreator interface {
|
||||
CreateLockboxClient(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (LockboxClient, error)
|
||||
CreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*IamToken, error)
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
type IamToken struct {
|
||||
Token string
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
// Responsible for accessing Lockbox secrets.
|
||||
type LockboxClient interface {
|
||||
GetPayloadEntries(ctx context.Context, secretID string, versionID string) ([]*lockbox.Payload_Entry, error)
|
||||
Close(ctx context.Context) error
|
||||
GetPayloadEntries(ctx context.Context, iamToken, secretID, versionID string) ([]*lockbox.Payload_Entry, error)
|
||||
}
|
||||
|
|
|
@ -16,45 +16,50 @@ package fake
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/uuid"
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client"
|
||||
)
|
||||
|
||||
// Fake implementation of LockboxClientCreator.
|
||||
type LockboxClientCreator struct {
|
||||
// Fake implementation of YandexCloudCreator.
|
||||
type YandexCloudCreator struct {
|
||||
Backend *LockboxBackend
|
||||
}
|
||||
|
||||
func (lcc *LockboxClientCreator) Create(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (client.LockboxClient, error) {
|
||||
return &LockboxClient{lcc.Backend, authorizedKey}, nil
|
||||
func (c *YandexCloudCreator) CreateLockboxClient(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (client.LockboxClient, error) {
|
||||
return &LockboxClient{c.Backend}, nil
|
||||
}
|
||||
|
||||
func (c *YandexCloudCreator) CreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*client.IamToken, error) {
|
||||
return c.Backend.getToken(authorizedKey)
|
||||
}
|
||||
|
||||
func (c *YandexCloudCreator) Now() time.Time {
|
||||
return c.Backend.now
|
||||
}
|
||||
|
||||
// Fake implementation of LockboxClient.
|
||||
type LockboxClient struct {
|
||||
fakeLockboxBackend *LockboxBackend
|
||||
authorizedKey *iamkey.Key
|
||||
}
|
||||
|
||||
func (lc *LockboxClient) GetPayloadEntries(ctx context.Context, secretID, versionID string) ([]*lockbox.Payload_Entry, error) {
|
||||
return lc.fakeLockboxBackend.getEntries(lc.authorizedKey, secretID, versionID)
|
||||
}
|
||||
|
||||
func (lc *LockboxClient) Close(ctx context.Context) error {
|
||||
return nil
|
||||
func (c *LockboxClient) GetPayloadEntries(ctx context.Context, iamToken, secretID, versionID string) ([]*lockbox.Payload_Entry, error) {
|
||||
return c.fakeLockboxBackend.getEntries(iamToken, secretID, versionID)
|
||||
}
|
||||
|
||||
// Fakes Yandex Lockbox service backend.
|
||||
type LockboxBackend struct {
|
||||
lastSecretID int // new secret IDs are generated by incrementing lastSecretID
|
||||
lastVersionID map[secretKey]int // new version IDs are generated by incrementing lastVersionID[secretKey]
|
||||
|
||||
secretMap map[secretKey]secretValue // secret specific data
|
||||
versionMap map[versionKey]versionValue // version specific data
|
||||
tokenMap map[tokenKey]tokenValue // token specific data
|
||||
|
||||
tokenExpirationDuration time.Duration
|
||||
now time.Time // fakes the current time
|
||||
}
|
||||
|
||||
type secretKey struct {
|
||||
|
@ -74,18 +79,28 @@ type versionValue struct {
|
|||
entries []*lockbox.Payload_Entry
|
||||
}
|
||||
|
||||
func NewLockboxBackend() *LockboxBackend {
|
||||
type tokenKey struct {
|
||||
token string
|
||||
}
|
||||
|
||||
type tokenValue struct {
|
||||
authorizedKey *iamkey.Key
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
func NewLockboxBackend(tokenExpirationDuration time.Duration) *LockboxBackend {
|
||||
return &LockboxBackend{
|
||||
lastSecretID: 0,
|
||||
lastVersionID: make(map[secretKey]int),
|
||||
secretMap: make(map[secretKey]secretValue),
|
||||
versionMap: make(map[versionKey]versionValue),
|
||||
secretMap: make(map[secretKey]secretValue),
|
||||
versionMap: make(map[versionKey]versionValue),
|
||||
tokenMap: make(map[tokenKey]tokenValue),
|
||||
tokenExpirationDuration: tokenExpirationDuration,
|
||||
now: time.Time{},
|
||||
}
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) CreateSecret(authorizedKey *iamkey.Key, entries ...*lockbox.Payload_Entry) (string, string) {
|
||||
secretID := lb.genSecretID()
|
||||
versionID := lb.genVersionID(secretID)
|
||||
secretID := uuid.NewString()
|
||||
versionID := uuid.NewString()
|
||||
|
||||
lb.secretMap[secretKey{secretID}] = secretValue{authorizedKey}
|
||||
lb.versionMap[versionKey{secretID, ""}] = versionValue{entries} // empty versionID corresponds to the latest version
|
||||
|
@ -95,7 +110,7 @@ func (lb *LockboxBackend) CreateSecret(authorizedKey *iamkey.Key, entries ...*lo
|
|||
}
|
||||
|
||||
func (lb *LockboxBackend) AddVersion(secretID string, entries ...*lockbox.Payload_Entry) string {
|
||||
versionID := lb.genVersionID(secretID)
|
||||
versionID := uuid.NewString()
|
||||
|
||||
lb.versionMap[versionKey{secretID, ""}] = versionValue{entries} // empty versionID corresponds to the latest version
|
||||
lb.versionMap[versionKey{secretID, versionID}] = versionValue{entries}
|
||||
|
@ -103,29 +118,34 @@ func (lb *LockboxBackend) AddVersion(secretID string, entries ...*lockbox.Payloa
|
|||
return versionID
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) getEntries(authorizedKey *iamkey.Key, secretID, versionID string) ([]*lockbox.Payload_Entry, error) {
|
||||
func (lb *LockboxBackend) AdvanceClock(duration time.Duration) {
|
||||
lb.now = lb.now.Add(duration)
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) getToken(authorizedKey *iamkey.Key) (*client.IamToken, error) {
|
||||
token := uuid.NewString()
|
||||
expiresAt := lb.now.Add(lb.tokenExpirationDuration)
|
||||
lb.tokenMap[tokenKey{token}] = tokenValue{authorizedKey, expiresAt}
|
||||
return &client.IamToken{Token: token, ExpiresAt: expiresAt}, nil
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) getEntries(iamToken, secretID, versionID string) ([]*lockbox.Payload_Entry, error) {
|
||||
if _, ok := lb.secretMap[secretKey{secretID}]; !ok {
|
||||
return nil, fmt.Errorf("secret not found")
|
||||
}
|
||||
if _, ok := lb.versionMap[versionKey{secretID, versionID}]; !ok {
|
||||
return nil, fmt.Errorf("version not found")
|
||||
}
|
||||
if !cmp.Equal(authorizedKey, lb.secretMap[secretKey{secretID}].expectedAuthorizedKey) {
|
||||
if _, ok := lb.tokenMap[tokenKey{iamToken}]; !ok {
|
||||
return nil, fmt.Errorf("unauthenticated")
|
||||
}
|
||||
|
||||
if lb.tokenMap[tokenKey{iamToken}].expiresAt.Before(lb.now) {
|
||||
return nil, fmt.Errorf("iam token expired")
|
||||
}
|
||||
if !cmp.Equal(lb.tokenMap[tokenKey{iamToken}].authorizedKey, lb.secretMap[secretKey{secretID}].expectedAuthorizedKey) {
|
||||
return nil, fmt.Errorf("permission denied")
|
||||
}
|
||||
|
||||
return lb.versionMap[versionKey{secretID, versionID}].entries, nil
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) genSecretID() string {
|
||||
lb.lastSecretID++
|
||||
return intToString(lb.lastSecretID)
|
||||
}
|
||||
|
||||
func (lb *LockboxBackend) genVersionID(secretID string) string {
|
||||
lb.lastVersionID[secretKey{secretID}]++
|
||||
return intToString(lb.lastVersionID[secretKey{secretID}])
|
||||
}
|
||||
|
||||
func intToString(i int) string {
|
||||
return strconv.FormatInt(int64(i), 10)
|
||||
}
|
||||
|
|
|
@ -15,55 +15,130 @@ package grpc
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/endpoint"
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
ycsdk "github.com/yandex-cloud/go-sdk"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client"
|
||||
)
|
||||
|
||||
// Implementation of LockboxClientCreator.
|
||||
type LockboxClientCreator struct {
|
||||
// Implementation of YandexCloudCreator.
|
||||
type YandexCloudCreator struct {
|
||||
}
|
||||
|
||||
func (lb *LockboxClientCreator) Create(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (client.LockboxClient, error) {
|
||||
credentials, err := ycsdk.ServiceAccountKey(authorizedKey)
|
||||
func (lb *YandexCloudCreator) CreateLockboxClient(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (client.LockboxClient, error) {
|
||||
sdk, err := buildSDK(ctx, apiEndpoint, authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payloadAPIEndpoint, err := sdk.ApiEndpoint().ApiEndpoint().Get(ctx, &endpoint.GetApiEndpointRequest{
|
||||
ApiEndpointId: "lockbox-payload", // the ID from https://api.cloud.yandex.net/endpoints
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = closeSDK(ctx, sdk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := grpc.Dial(payloadAPIEndpoint.Address,
|
||||
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12})),
|
||||
grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: time.Second * 30,
|
||||
Timeout: time.Second * 10,
|
||||
PermitWithoutStream: false,
|
||||
}),
|
||||
grpc.WithUserAgent("external-secrets"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LockboxClient{lockbox.NewPayloadServiceClient(conn)}, nil
|
||||
}
|
||||
|
||||
func (lb *YandexCloudCreator) CreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*client.IamToken, error) {
|
||||
sdk, err := buildSDK(ctx, apiEndpoint, authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iamToken, err := sdk.CreateIAMToken(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = closeSDK(ctx, sdk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &client.IamToken{Token: iamToken.IamToken, ExpiresAt: iamToken.ExpiresAt.AsTime()}, nil
|
||||
}
|
||||
|
||||
func (lb *YandexCloudCreator) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func buildSDK(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*ycsdk.SDK, error) {
|
||||
creds, err := ycsdk.ServiceAccountKey(authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sdk, err := ycsdk.Build(ctx, ycsdk.Config{
|
||||
Credentials: credentials,
|
||||
Credentials: creds,
|
||||
Endpoint: apiEndpoint,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LockboxClient{sdk}, nil
|
||||
return sdk, nil
|
||||
}
|
||||
|
||||
func closeSDK(ctx context.Context, sdk *ycsdk.SDK) error {
|
||||
return sdk.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// Implementation of LockboxClient.
|
||||
type LockboxClient struct {
|
||||
sdk *ycsdk.SDK
|
||||
lockboxPayloadClient lockbox.PayloadServiceClient
|
||||
}
|
||||
|
||||
func (lb *LockboxClient) GetPayloadEntries(ctx context.Context, secretID, versionID string) ([]*lockbox.Payload_Entry, error) {
|
||||
payload, err := lb.sdk.LockboxPayload().Payload().Get(ctx, &lockbox.GetPayloadRequest{
|
||||
SecretId: secretID,
|
||||
VersionId: versionID,
|
||||
})
|
||||
func (lc *LockboxClient) GetPayloadEntries(ctx context.Context, iamToken, secretID, versionID string) ([]*lockbox.Payload_Entry, error) {
|
||||
payload, err := lc.lockboxPayloadClient.Get(
|
||||
ctx,
|
||||
&lockbox.GetPayloadRequest{
|
||||
SecretId: secretID,
|
||||
VersionId: versionID,
|
||||
},
|
||||
grpc.PerRPCCredentials(perRPCCredentials{iamToken: iamToken}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload.Entries, nil
|
||||
}
|
||||
|
||||
func (lb *LockboxClient) Close(ctx context.Context) error {
|
||||
err := lb.sdk.Shutdown(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
type perRPCCredentials struct {
|
||||
iamToken string
|
||||
}
|
||||
|
||||
func (t perRPCCredentials) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
|
||||
return map[string]string{"Authorization": "Bearer " + t.iamToken}, nil
|
||||
}
|
||||
|
||||
func (perRPCCredentials) RequireTransportSecurity() bool {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -15,13 +15,18 @@ package lockbox
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
|
@ -31,9 +36,33 @@ import (
|
|||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/grpc"
|
||||
)
|
||||
|
||||
const maxSecretsClientLifetime = 5 * time.Minute // supposed SecretsClient lifetime is quite short
|
||||
const iamTokenCleanupDelay = 1 * time.Hour // specifies how often cleanUpIamTokenMap() is performed
|
||||
|
||||
var log = ctrl.Log.WithName("provider").WithName("yandex").WithName("lockbox")
|
||||
|
||||
type iamTokenKey struct {
|
||||
authorizedKeyID string
|
||||
serviceAccountID string
|
||||
privateKeyHash string
|
||||
}
|
||||
|
||||
// lockboxProvider is a provider for Yandex Lockbox.
|
||||
type lockboxProvider struct {
|
||||
lockboxClientCreator client.LockboxClientCreator
|
||||
yandexCloudCreator client.YandexCloudCreator
|
||||
|
||||
lockboxClientMap map[string]client.LockboxClient // apiEndpoint -> LockboxClient
|
||||
lockboxClientMapMutex sync.Mutex
|
||||
iamTokenMap map[iamTokenKey]*client.IamToken
|
||||
iamTokenMapMutex sync.Mutex
|
||||
}
|
||||
|
||||
func newLockboxProvider(yandexCloudCreator client.YandexCloudCreator) *lockboxProvider {
|
||||
return &lockboxProvider{
|
||||
yandexCloudCreator: yandexCloudCreator,
|
||||
lockboxClientMap: make(map[string]client.LockboxClient),
|
||||
iamTokenMap: make(map[iamTokenKey]*client.IamToken),
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient constructs a Yandex Lockbox Provider.
|
||||
|
@ -78,22 +107,99 @@ func (p *lockboxProvider) NewClient(ctx context.Context, store esv1alpha1.Generi
|
|||
return nil, fmt.Errorf("unable to unmarshal authorized key: %w", err)
|
||||
}
|
||||
|
||||
lb, err := p.lockboxClientCreator.Create(ctx, storeSpecYandexLockbox.APIEndpoint, &authorizedKey)
|
||||
lockboxClient, err := p.getOrCreateLockboxClient(ctx, storeSpecYandexLockbox.APIEndpoint, &authorizedKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Yandex.Cloud SDK: %w", err)
|
||||
return nil, fmt.Errorf("failed to create Yandex Lockbox client: %w", err)
|
||||
}
|
||||
|
||||
return &lockboxSecretsClient{lb}, nil
|
||||
iamToken, err := p.getOrCreateIamToken(ctx, storeSpecYandexLockbox.APIEndpoint, &authorizedKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create IAM token: %w", err)
|
||||
}
|
||||
|
||||
return &lockboxSecretsClient{lockboxClient, iamToken.Token}, nil
|
||||
}
|
||||
|
||||
func (p *lockboxProvider) getOrCreateLockboxClient(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (client.LockboxClient, error) {
|
||||
p.lockboxClientMapMutex.Lock()
|
||||
defer p.lockboxClientMapMutex.Unlock()
|
||||
|
||||
if _, ok := p.lockboxClientMap[apiEndpoint]; !ok {
|
||||
log.Info("creating LockboxClient", "apiEndpoint", apiEndpoint)
|
||||
|
||||
lockboxClient, err := p.yandexCloudCreator.CreateLockboxClient(ctx, apiEndpoint, authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.lockboxClientMap[apiEndpoint] = lockboxClient
|
||||
}
|
||||
return p.lockboxClientMap[apiEndpoint], nil
|
||||
}
|
||||
|
||||
func (p *lockboxProvider) getOrCreateIamToken(ctx context.Context, apiEndpoint string, authorizedKey *iamkey.Key) (*client.IamToken, error) {
|
||||
p.iamTokenMapMutex.Lock()
|
||||
defer p.iamTokenMapMutex.Unlock()
|
||||
|
||||
iamTokenKey := buildIamTokenKey(authorizedKey)
|
||||
if iamToken, ok := p.iamTokenMap[iamTokenKey]; !ok || !p.isIamTokenUsable(iamToken) {
|
||||
log.Info("creating IAM token", "authorizedKeyId", authorizedKey.Id)
|
||||
|
||||
iamToken, err := p.yandexCloudCreator.CreateIamToken(ctx, apiEndpoint, authorizedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("created IAM token", "authorizedKeyId", authorizedKey.Id, "expiresAt", iamToken.ExpiresAt)
|
||||
|
||||
p.iamTokenMap[iamTokenKey] = iamToken
|
||||
}
|
||||
return p.iamTokenMap[iamTokenKey], nil
|
||||
}
|
||||
|
||||
func (p *lockboxProvider) isIamTokenUsable(iamToken *client.IamToken) bool {
|
||||
now := p.yandexCloudCreator.Now()
|
||||
return now.Add(maxSecretsClientLifetime).Before(iamToken.ExpiresAt)
|
||||
}
|
||||
|
||||
func buildIamTokenKey(authorizedKey *iamkey.Key) iamTokenKey {
|
||||
privateKeyHash := sha256.Sum256([]byte(authorizedKey.PrivateKey))
|
||||
return iamTokenKey{
|
||||
authorizedKey.GetId(),
|
||||
authorizedKey.GetServiceAccountId(),
|
||||
hex.EncodeToString(privateKeyHash[:]),
|
||||
}
|
||||
}
|
||||
|
||||
// Used for testing.
|
||||
func (p *lockboxProvider) isIamTokenCached(authorizedKey *iamkey.Key) bool {
|
||||
p.iamTokenMapMutex.Lock()
|
||||
defer p.iamTokenMapMutex.Unlock()
|
||||
|
||||
_, ok := p.iamTokenMap[buildIamTokenKey(authorizedKey)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (p *lockboxProvider) cleanUpIamTokenMap() {
|
||||
p.iamTokenMapMutex.Lock()
|
||||
defer p.iamTokenMapMutex.Unlock()
|
||||
|
||||
for key, value := range p.iamTokenMap {
|
||||
if p.yandexCloudCreator.Now().After(value.ExpiresAt) {
|
||||
log.Info("deleting IAM token", "authorizedKeyId", key.authorizedKeyID)
|
||||
delete(p.iamTokenMap, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lockboxSecretsClient is a secrets client for Yandex Lockbox.
|
||||
type lockboxSecretsClient struct {
|
||||
lockboxClient client.LockboxClient
|
||||
iamToken string
|
||||
}
|
||||
|
||||
// GetSecret returns a single secret from the provider.
|
||||
func (p *lockboxSecretsClient) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
entries, err := p.lockboxClient.GetPayloadEntries(ctx, ref.Key, ref.Version)
|
||||
func (c *lockboxSecretsClient) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
entries, err := c.lockboxClient.GetPayloadEntries(ctx, c.iamToken, ref.Key, ref.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to request secret payload to get secret: %w", err)
|
||||
}
|
||||
|
@ -122,8 +228,8 @@ func (p *lockboxSecretsClient) GetSecret(ctx context.Context, ref esv1alpha1.Ext
|
|||
}
|
||||
|
||||
// GetSecretMap returns multiple k/v pairs from the provider.
|
||||
func (p *lockboxSecretsClient) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
entries, err := p.lockboxClient.GetPayloadEntries(ctx, ref.Key, ref.Version)
|
||||
func (c *lockboxSecretsClient) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
entries, err := c.lockboxClient.GetPayloadEntries(ctx, c.iamToken, ref.Key, ref.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to request secret payload to get secret map: %w", err)
|
||||
}
|
||||
|
@ -139,8 +245,8 @@ func (p *lockboxSecretsClient) GetSecretMap(ctx context.Context, ref esv1alpha1.
|
|||
return secretMap, nil
|
||||
}
|
||||
|
||||
func (p *lockboxSecretsClient) Close(ctx context.Context) error {
|
||||
return p.lockboxClient.Close(ctx)
|
||||
func (c *lockboxSecretsClient) Close(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getValueAsIs(entry *lockbox.Payload_Entry) (interface{}, error) {
|
||||
|
@ -175,10 +281,17 @@ func findEntryByKey(entries []*lockbox.Payload_Entry, key string) (*lockbox.Payl
|
|||
}
|
||||
|
||||
func init() {
|
||||
lockboxProvider := newLockboxProvider(&grpc.YandexCloudCreator{})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(iamTokenCleanupDelay)
|
||||
lockboxProvider.cleanUpIamTokenMap()
|
||||
}
|
||||
}()
|
||||
|
||||
schema.Register(
|
||||
&lockboxProvider{
|
||||
lockboxClientCreator: &grpc.LockboxClientCreator{},
|
||||
},
|
||||
lockboxProvider,
|
||||
&esv1alpha1.SecretStoreProvider{
|
||||
YandexLockbox: &esv1alpha1.YandexLockboxProvider{},
|
||||
},
|
||||
|
|
|
@ -18,7 +18,9 @@ import (
|
|||
b64 "encoding/base64"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
tassert "github.com/stretchr/testify/assert"
|
||||
"github.com/yandex-cloud/go-genproto/yandex/cloud/lockbox/v1"
|
||||
"github.com/yandex-cloud/go-sdk/iamkey"
|
||||
|
@ -73,19 +75,19 @@ func TestNewClient(t *testing.T) {
|
|||
tassert.EqualError(t, err, "could not fetch AuthorizedKey secret: secrets \"authorizedKeySecretName\" not found")
|
||||
tassert.Nil(t, secretClient)
|
||||
|
||||
err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, newFakeAuthorizedKey("0"))
|
||||
err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, newFakeAuthorizedKey())
|
||||
tassert.Nil(t, err)
|
||||
secretClient, err = provider.NewClient(context.Background(), store, k8sClient, namespace)
|
||||
tassert.EqualError(t, err, "failed to create Yandex.Cloud SDK: private key parsing failed: Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
|
||||
tassert.EqualError(t, err, "failed to create Yandex Lockbox client: private key parsing failed: Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
|
||||
tassert.Nil(t, secretClient)
|
||||
}
|
||||
|
||||
func TestGetSecretForAllEntries(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
authorizedKey := newFakeAuthorizedKey("0")
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend()
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
k2, v2 := "k2", []byte("v2")
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
|
@ -98,11 +100,11 @@ func TestGetSecretForAllEntries(t *testing.T) {
|
|||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := &lockboxProvider{&fake.LockboxClientCreator{
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
}}
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
|
||||
|
@ -120,10 +122,10 @@ func TestGetSecretForAllEntries(t *testing.T) {
|
|||
|
||||
func TestGetSecretForTextEntry(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
authorizedKey := newFakeAuthorizedKey("0")
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend()
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
k2, v2 := "k2", []byte("v2")
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
|
@ -136,11 +138,11 @@ func TestGetSecretForTextEntry(t *testing.T) {
|
|||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := &lockboxProvider{&fake.LockboxClientCreator{
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
}}
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
|
||||
|
@ -151,10 +153,10 @@ func TestGetSecretForTextEntry(t *testing.T) {
|
|||
|
||||
func TestGetSecretForBinaryEntry(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
authorizedKey := newFakeAuthorizedKey("0")
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend()
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
k2, v2 := "k2", []byte("v2")
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
|
@ -167,11 +169,11 @@ func TestGetSecretForBinaryEntry(t *testing.T) {
|
|||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := &lockboxProvider{&fake.LockboxClientCreator{
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
}}
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k2})
|
||||
|
@ -182,10 +184,10 @@ func TestGetSecretForBinaryEntry(t *testing.T) {
|
|||
|
||||
func TestGetSecretByVersionID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
authorizedKey := newFakeAuthorizedKey("0")
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend()
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
oldKey, oldVal := "oldKey", "oldVal"
|
||||
secretID, oldVersionID := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(oldKey, oldVal),
|
||||
|
@ -196,11 +198,11 @@ func TestGetSecretByVersionID(t *testing.T) {
|
|||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := &lockboxProvider{&fake.LockboxClientCreator{
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
}}
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
|
||||
|
@ -224,11 +226,11 @@ func TestGetSecretByVersionID(t *testing.T) {
|
|||
|
||||
func TestGetSecretUnauthorized(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
authorizedKeyA := newFakeAuthorizedKey("A")
|
||||
authorizedKeyB := newFakeAuthorizedKey("B")
|
||||
namespace := uuid.NewString()
|
||||
authorizedKeyA := newFakeAuthorizedKey()
|
||||
authorizedKeyB := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend()
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKeyA,
|
||||
textEntry("k1", "v1"),
|
||||
)
|
||||
|
@ -238,11 +240,11 @@ func TestGetSecretUnauthorized(t *testing.T) {
|
|||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKeyB)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := &lockboxProvider{&fake.LockboxClientCreator{
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
}}
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
|
||||
|
@ -251,21 +253,21 @@ func TestGetSecretUnauthorized(t *testing.T) {
|
|||
|
||||
func TestGetSecretNotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
authorizedKey := newFakeAuthorizedKey("0")
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend()
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := &lockboxProvider{&fake.LockboxClientCreator{
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
}}
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: "no-secret-with-this-id"})
|
||||
|
@ -278,12 +280,245 @@ func TestGetSecretNotFound(t *testing.T) {
|
|||
tassert.EqualError(t, err, "unable to request secret payload to get secret: version not found")
|
||||
}
|
||||
|
||||
func TestGetSecretWithTwoNamespaces(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace1 := uuid.NewString()
|
||||
namespace2 := uuid.NewString()
|
||||
authorizedKey1 := newFakeAuthorizedKey()
|
||||
authorizedKey2 := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
secretID1, _ := lockboxBackend.CreateSecret(authorizedKey1,
|
||||
textEntry(k1, v1),
|
||||
)
|
||||
k2, v2 := "k2", "v2"
|
||||
secretID2, _ := lockboxBackend.CreateSecret(authorizedKey2,
|
||||
textEntry(k2, v2),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace1, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey1)
|
||||
tassert.Nil(t, err)
|
||||
err = createK8sSecret(ctx, k8sClient, namespace2, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey2)
|
||||
tassert.Nil(t, err)
|
||||
store1 := newYandexLockboxSecretStore("", namespace1, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store2 := newYandexLockboxSecretStore("", namespace2, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
secretsClient1, err := provider.NewClient(ctx, store1, k8sClient, namespace1)
|
||||
tassert.Nil(t, err)
|
||||
secretsClient2, err := provider.NewClient(ctx, store2, k8sClient, namespace2)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
data, err := secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
|
||||
tassert.Equal(t, v1, string(data))
|
||||
tassert.Nil(t, err)
|
||||
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: permission denied")
|
||||
|
||||
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: permission denied")
|
||||
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
tassert.Equal(t, v2, string(data))
|
||||
tassert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestGetSecretWithTwoApiEndpoints(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
apiEndpoint1 := uuid.NewString()
|
||||
apiEndpoint2 := uuid.NewString()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey1 := newFakeAuthorizedKey()
|
||||
authorizedKey2 := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend1 := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
secretID1, _ := lockboxBackend1.CreateSecret(authorizedKey1,
|
||||
textEntry(k1, v1),
|
||||
)
|
||||
lockboxBackend2 := fake.NewLockboxBackend(time.Hour)
|
||||
k2, v2 := "k2", "v2"
|
||||
secretID2, _ := lockboxBackend2.CreateSecret(authorizedKey2,
|
||||
textEntry(k2, v2),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName1 = "authorizedKeySecretName1"
|
||||
const authorizedKeySecretKey1 = "authorizedKeySecretKey1"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName1, authorizedKeySecretKey1, authorizedKey1)
|
||||
tassert.Nil(t, err)
|
||||
const authorizedKeySecretName2 = "authorizedKeySecretName2"
|
||||
const authorizedKeySecretKey2 = "authorizedKeySecretKey2"
|
||||
err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName2, authorizedKeySecretKey2, authorizedKey2)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
store1 := newYandexLockboxSecretStore(apiEndpoint1, namespace, authorizedKeySecretName1, authorizedKeySecretKey1)
|
||||
store2 := newYandexLockboxSecretStore(apiEndpoint2, namespace, authorizedKeySecretName2, authorizedKeySecretKey2)
|
||||
|
||||
provider1 := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend1,
|
||||
})
|
||||
provider2 := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend2,
|
||||
})
|
||||
|
||||
secretsClient1, err := provider1.NewClient(ctx, store1, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
secretsClient2, err := provider2.NewClient(ctx, store2, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
var data []byte
|
||||
|
||||
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
|
||||
tassert.Equal(t, v1, string(data))
|
||||
tassert.Nil(t, err)
|
||||
data, err = secretsClient1.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: secret not found")
|
||||
|
||||
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1, Property: k1})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: secret not found")
|
||||
data, err = secretsClient2.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2, Property: k2})
|
||||
tassert.Equal(t, v2, string(data))
|
||||
tassert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestGetSecretWithIamTokenExpiration(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
tokenExpirationTime := time.Hour
|
||||
lockboxBackend := fake.NewLockboxBackend(tokenExpirationTime)
|
||||
k1, v1 := "k1", "v1"
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(k1, v1),
|
||||
)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName = "authorizedKeySecretName"
|
||||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
|
||||
var data []byte
|
||||
|
||||
oldSecretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err = oldSecretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
|
||||
tassert.Equal(t, v1, string(data))
|
||||
tassert.Nil(t, err)
|
||||
|
||||
lockboxBackend.AdvanceClock(2 * tokenExpirationTime)
|
||||
|
||||
data, err = oldSecretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
|
||||
tassert.Nil(t, data)
|
||||
tassert.EqualError(t, err, "unable to request secret payload to get secret: iam token expired")
|
||||
|
||||
newSecretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err = newSecretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Property: k1})
|
||||
tassert.Equal(t, v1, string(data))
|
||||
tassert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestGetSecretWithIamTokenCleanup(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey1 := newFakeAuthorizedKey()
|
||||
authorizedKey2 := newFakeAuthorizedKey()
|
||||
|
||||
tokenExpirationDuration := time.Hour
|
||||
lockboxBackend := fake.NewLockboxBackend(tokenExpirationDuration)
|
||||
secretID1, _ := lockboxBackend.CreateSecret(authorizedKey1,
|
||||
textEntry("k1", "v1"),
|
||||
)
|
||||
secretID2, _ := lockboxBackend.CreateSecret(authorizedKey2,
|
||||
textEntry("k2", "v2"),
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
const authorizedKeySecretName1 = "authorizedKeySecretName1"
|
||||
const authorizedKeySecretKey1 = "authorizedKeySecretKey1"
|
||||
err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName1, authorizedKeySecretKey1, authorizedKey1)
|
||||
tassert.Nil(t, err)
|
||||
const authorizedKeySecretName2 = "authorizedKeySecretName2"
|
||||
const authorizedKeySecretKey2 = "authorizedKeySecretKey2"
|
||||
err = createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName2, authorizedKeySecretKey2, authorizedKey2)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
store1 := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName1, authorizedKeySecretKey1)
|
||||
store2 := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName2, authorizedKeySecretKey2)
|
||||
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
})
|
||||
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
// Access secretID1 with authorizedKey1, IAM token for authorizedKey1 should be cached
|
||||
secretsClient, err := provider.NewClient(ctx, store1, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID1})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
lockboxBackend.AdvanceClock(tokenExpirationDuration * 2)
|
||||
|
||||
// Access secretID2 with authorizedKey2, IAM token for authorizedKey2 should be cached
|
||||
secretsClient, err = provider.NewClient(ctx, store2, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
_, err = secretsClient.GetSecret(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID2})
|
||||
tassert.Nil(t, err)
|
||||
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
lockboxBackend.AdvanceClock(tokenExpirationDuration)
|
||||
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
provider.cleanUpIamTokenMap()
|
||||
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
lockboxBackend.AdvanceClock(tokenExpirationDuration)
|
||||
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.True(t, provider.isIamTokenCached(authorizedKey2))
|
||||
|
||||
provider.cleanUpIamTokenMap()
|
||||
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey1))
|
||||
tassert.False(t, provider.isIamTokenCached(authorizedKey2))
|
||||
}
|
||||
|
||||
func TestGetSecretMap(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
authorizedKey := newFakeAuthorizedKey("0")
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend()
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
k1, v1 := "k1", "v1"
|
||||
k2, v2 := "k2", []byte("v2")
|
||||
secretID, _ := lockboxBackend.CreateSecret(authorizedKey,
|
||||
|
@ -296,11 +531,11 @@ func TestGetSecretMap(t *testing.T) {
|
|||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := &lockboxProvider{&fake.LockboxClientCreator{
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
}}
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID})
|
||||
|
@ -318,10 +553,10 @@ func TestGetSecretMap(t *testing.T) {
|
|||
|
||||
func TestGetSecretMapByVersionID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
const namespace = "namespace"
|
||||
authorizedKey := newFakeAuthorizedKey("0")
|
||||
namespace := uuid.NewString()
|
||||
authorizedKey := newFakeAuthorizedKey()
|
||||
|
||||
lockboxBackend := fake.NewLockboxBackend()
|
||||
lockboxBackend := fake.NewLockboxBackend(time.Hour)
|
||||
oldKey, oldVal := "oldKey", "oldVal"
|
||||
secretID, oldVersionID := lockboxBackend.CreateSecret(authorizedKey,
|
||||
textEntry(oldKey, oldVal),
|
||||
|
@ -332,11 +567,11 @@ func TestGetSecretMapByVersionID(t *testing.T) {
|
|||
const authorizedKeySecretKey = "authorizedKeySecretKey"
|
||||
err := createK8sSecret(ctx, k8sClient, namespace, authorizedKeySecretName, authorizedKeySecretKey, authorizedKey)
|
||||
tassert.Nil(t, err)
|
||||
store := newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
store := newYandexLockboxSecretStore("", namespace, authorizedKeySecretName, authorizedKeySecretKey)
|
||||
|
||||
provider := &lockboxProvider{&fake.LockboxClientCreator{
|
||||
provider := newLockboxProvider(&fake.YandexCloudCreator{
|
||||
Backend: lockboxBackend,
|
||||
}}
|
||||
})
|
||||
secretsClient, err := provider.NewClient(ctx, store, k8sClient, namespace)
|
||||
tassert.Nil(t, err)
|
||||
data, err := secretsClient.GetSecretMap(ctx, esv1alpha1.ExternalSecretDataRemoteRef{Key: secretID, Version: oldVersionID})
|
||||
|
@ -360,7 +595,7 @@ func TestGetSecretMapByVersionID(t *testing.T) {
|
|||
|
||||
// helper functions
|
||||
|
||||
func newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedKeySecretKey string) esv1alpha1.GenericStore {
|
||||
func newYandexLockboxSecretStore(apiEndpoint, namespace, authorizedKeySecretName, authorizedKeySecretKey string) esv1alpha1.GenericStore {
|
||||
return &esv1alpha1.SecretStore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
|
@ -368,6 +603,7 @@ func newYandexLockboxSecretStore(namespace, authorizedKeySecretName, authorizedK
|
|||
Spec: esv1alpha1.SecretStoreSpec{
|
||||
Provider: &esv1alpha1.SecretStoreProvider{
|
||||
YandexLockbox: &esv1alpha1.YandexLockboxProvider{
|
||||
APIEndpoint: apiEndpoint,
|
||||
Auth: esv1alpha1.YandexLockboxAuth{
|
||||
AuthorizedKey: esmeta.SecretKeySelector{
|
||||
Name: authorizedKeySecretName,
|
||||
|
@ -400,7 +636,8 @@ func createK8sSecret(ctx context.Context, k8sClient client.Client, namespace, se
|
|||
return nil
|
||||
}
|
||||
|
||||
func newFakeAuthorizedKey(uniqueLabel string) *iamkey.Key {
|
||||
func newFakeAuthorizedKey() *iamkey.Key {
|
||||
uniqueLabel := uuid.NewString()
|
||||
return &iamkey.Key{
|
||||
Id: uniqueLabel,
|
||||
Subject: &iamkey.Key_ServiceAccountId{
|
||||
|
|
Loading…
Reference in a new issue