Fetch secret store config and complete connection manager

Signed-off-by: Hasan Turken <turkenh@gmail.com>
This commit is contained in:
Hasan Turken 2022-02-10 00:27:55 +03:00
parent 31c8287de6
commit 19034f22d1
No known key found for this signature in database
GPG Key ID: D7AA042F8F8B488E
9 changed files with 262 additions and 130 deletions

View File

@ -18,7 +18,7 @@ type PublishConnectionDetailsTo struct {
// SecretStoreConfigRef specifies which secret store config should be used
// for this ConnectionSecret.
// +kubebuilder:default={"name": "default"}
SecretStoreConfigRef *Reference `json:"configRef,omitempty"`
SecretStoreConfigRef Reference `json:"configRef,omitempty"`
}
// SecretStoreType represents a secret store type.
@ -45,8 +45,8 @@ type SecretStoreConfig struct {
// If store type is "Kubernetes", this would mean the default namespace to
// store connection secrets for cluster scoped resources.
// In case of "Vault", this would be used as the default parent path.
// If not provided, Crossplane installation namespace will be used instead.
DefaultScope *string `json:"defaultScope,omitempty"`
// Typically, should be set as Crossplane installation namespace.
DefaultScope string `json:"defaultScope,omitempty"`
// Kubernetes configures a Kubernetes secret store.
// If the "type" is "Kubernetes" but no config provided, in cluster config

View File

@ -93,43 +93,6 @@ func (in *ConditionedStatus) DeepCopy() *ConditionedStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PublishConnectionDetailsTo) DeepCopyInto(out *PublishConnectionDetailsTo) {
*out = *in
if in.Metadata != nil {
in, out := &in.Metadata, &out.Metadata
*out = make(ConnectionSecretMetadata, len(*in))
for key, val := range *in {
var outVal map[string]string
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
(*out)[key] = outVal
}
}
if in.SecretStoreConfigRef != nil {
in, out := &in.SecretStoreConfigRef, &out.SecretStoreConfigRef
*out = new(Reference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublishConnectionDetailsTo.
func (in *PublishConnectionDetailsTo) DeepCopy() *PublishConnectionDetailsTo {
if in == nil {
return nil
}
out := new(PublishConnectionDetailsTo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in ConnectionSecretMetadata) DeepCopyInto(out *ConnectionSecretMetadata) {
{
@ -208,17 +171,17 @@ func (in *KubernetesAuthConfig) DeepCopy() *KubernetesAuthConfig {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubernetesSecretStoreProviderConfig) DeepCopyInto(out *KubernetesSecretStoreProviderConfig) {
func (in *KubernetesSecretStoreConfig) DeepCopyInto(out *KubernetesSecretStoreConfig) {
*out = *in
in.Auth.DeepCopyInto(&out.Auth)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesSecretStoreProviderConfig.
func (in *KubernetesSecretStoreProviderConfig) DeepCopy() *KubernetesSecretStoreProviderConfig {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesSecretStoreConfig.
func (in *KubernetesSecretStoreConfig) DeepCopy() *KubernetesSecretStoreConfig {
if in == nil {
return nil
}
out := new(KubernetesSecretStoreProviderConfig)
out := new(KubernetesSecretStoreConfig)
in.DeepCopyInto(out)
return out
}
@ -296,6 +259,39 @@ func (in *ProviderConfigUsage) DeepCopy() *ProviderConfigUsage {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PublishConnectionDetailsTo) DeepCopyInto(out *PublishConnectionDetailsTo) {
*out = *in
if in.Metadata != nil {
in, out := &in.Metadata, &out.Metadata
*out = make(ConnectionSecretMetadata, len(*in))
for key, val := range *in {
var outVal map[string]string
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
(*out)[key] = outVal
}
}
out.SecretStoreConfigRef = in.SecretStoreConfigRef
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PublishConnectionDetailsTo.
func (in *PublishConnectionDetailsTo) DeepCopy() *PublishConnectionDetailsTo {
if in == nil {
return nil
}
out := new(PublishConnectionDetailsTo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Reference) DeepCopyInto(out *Reference) {
*out = *in
@ -396,19 +392,14 @@ func (in *SecretReference) DeepCopy() *SecretReference {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretStoreConfig) DeepCopyInto(out *SecretStoreConfig) {
*out = *in
if in.DefaultScope != nil {
in, out := &in.DefaultScope, &out.DefaultScope
*out = new(string)
**out = **in
}
if in.Kubernetes != nil {
in, out := &in.Kubernetes, &out.Kubernetes
*out = new(KubernetesSecretStoreProviderConfig)
*out = new(KubernetesSecretStoreConfig)
(*in).DeepCopyInto(*out)
}
if in.Vault != nil {
in, out := &in.Vault, &out.Vault
*out = new(VaultSecretStoreProviderConfig)
*out = new(VaultSecretStoreConfig)
(*in).DeepCopyInto(*out)
}
}
@ -542,7 +533,7 @@ func (in *VaultAuthKubernetesConfig) DeepCopy() *VaultAuthKubernetesConfig {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultSecretStoreProviderConfig) DeepCopyInto(out *VaultSecretStoreProviderConfig) {
func (in *VaultSecretStoreConfig) DeepCopyInto(out *VaultSecretStoreConfig) {
*out = *in
if in.CABundleSecretRef != nil {
in, out := &in.CABundleSecretRef, &out.CABundleSecretRef
@ -552,12 +543,12 @@ func (in *VaultSecretStoreProviderConfig) DeepCopyInto(out *VaultSecretStoreProv
in.Auth.DeepCopyInto(&out.Auth)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultSecretStoreProviderConfig.
func (in *VaultSecretStoreProviderConfig) DeepCopy() *VaultSecretStoreProviderConfig {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultSecretStoreConfig.
func (in *VaultSecretStoreConfig) DeepCopy() *VaultSecretStoreConfig {
if in == nil {
return nil
}
out := new(VaultSecretStoreProviderConfig)
out := new(VaultSecretStoreConfig)
in.DeepCopyInto(out)
return out
}

View File

@ -3,47 +3,79 @@ package connection
import (
"context"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/connection/secret/kubernetes"
"github.com/crossplane/crossplane-runtime/pkg/connection/secret"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/secret/kubernetes"
"github.com/crossplane/crossplane-runtime/pkg/connection/secret/store"
"github.com/crossplane/crossplane-runtime/pkg/connection/secret/vault"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/logging"
"github.com/crossplane/crossplane-runtime/pkg/meta"
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// Error strings.
const (
errExtractKubernetesAuthCreds = "cannot extract kubernetes auth credentials"
errConnectStore = "cannot connect to secret store"
errWriteStore = "cannot write to secret store"
errDeleteFromStore = "cannot delete from secret store"
errGetStoreConfig = "cannot get store config"
errFmtUnknownSecretStore = "unknown secret store type: %d"
errFmtUnknownSecretStore = "unknown secret store type: %q"
)
type SecretStoreManager struct {
client client.Client
typer runtime.ObjectTyper
type StoreConfigProvider interface {
GetStoreConfig() v1.SecretStoreConfig
}
type StoreConfig interface {
resource.Object
resource.Conditioned
StoreConfigProvider
}
// A StoreConfigKind contains the type metadata for a kind of StoreConfig
// resource.
type StoreConfigKind schema.GroupVersionKind
type StoreBuilderFn func(ctx context.Context, local client.Client, cfg v1.SecretStoreConfig) (store.Store, error)
type Manager struct {
client client.Client
typer runtime.ObjectTyper
newStoreConfig func() StoreConfig
storeBuilders map[v1.SecretStoreType]StoreBuilderFn
log logging.Logger
}
type SecretStoreManagerOption func(*SecretStoreManager)
type ManagerOption func(*Manager)
func WithLogger(l logging.Logger) SecretStoreManagerOption {
return func(m *SecretStoreManager) {
func WithLogger(l logging.Logger) ManagerOption {
return func(m *Manager) {
m.log = l
}
}
func NewSecretStoreManager(c client.Client, ot runtime.ObjectTyper, o ...SecretStoreManagerOption) *SecretStoreManager {
m := &SecretStoreManager{
client: c,
typer: ot,
func NewManager(c client.Client, ot runtime.ObjectTyper, of StoreConfigKind, o ...ManagerOption) *Manager {
nsc := func() StoreConfig {
return store.NewConfig(store.ConfigWithGroupVersionKind(schema.GroupVersionKind(of)))
}
m := &Manager{
client: c,
typer: ot,
newStoreConfig: nsc,
storeBuilders: map[v1.SecretStoreType]StoreBuilderFn{
v1.SecretStoreKubernetes: kubernetes.NewSecretStore,
v1.SecretStoreVault: vault.NewSecretStore,
},
log: logging.NewNopLogger(),
}
@ -55,26 +87,57 @@ func NewSecretStoreManager(c client.Client, ot runtime.ObjectTyper, o ...SecretS
return m
}
func (m *SecretStoreManager) connectToStore(ctx context.Context, cfg v1.SecretStoreConfig) (secret.Store, error) {
switch cfg.Type {
case v1.SecretStoreKubernetes:
return kubernetes.NewSecretStore(ctx, m.client, cfg.Kubernetes)
case v1.SecretStoreVault:
return nil, nil
default:
func (m *Manager) connectStore(ctx context.Context, p *v1.PublishConnectionDetailsTo) (store.Store, error) {
sc := m.newStoreConfig()
if err := m.client.Get(ctx, types.NamespacedName{Name: p.SecretStoreConfigRef.Name}, sc); err != nil {
return nil, errors.Wrap(resource.IgnoreNotFound(err), errGetStoreConfig)
}
cfg := sc.GetStoreConfig()
sb, ok := m.storeBuilders[cfg.Type]
if !ok {
return nil, errors.Errorf(errFmtUnknownSecretStore, cfg.Type)
}
return sb(ctx, m.client, cfg)
}
func (m *SecretStoreManager) ValidateConfig(ctx context.Context) error {
m.log.Info("validating config...")
return nil
func (m *Manager) PublishConnection(ctx context.Context, mg resource.Managed, c managed.ConnectionDetails) error {
// This resource does not want to expose a connection secret.
p := mg.GetPublishConnectionDetailsTo()
if p == nil {
return nil
}
ss, err := m.connectStore(ctx, p)
if err != nil {
return errors.Wrap(err, errConnectStore)
}
return errors.Wrap(ss.WriteKeyValues(ctx, store.SecretInstance{
Name: p.Name,
Scope: mg.GetNamespace(),
Owner: meta.AsController(meta.TypedReferenceTo(mg, resource.MustGetKind(mg, m.typer))),
Metadata: p.Metadata,
}, store.KeyValues(c)), errWriteStore)
}
func (m *SecretStoreManager) PublishConnection(ctx context.Context, mg resource.Managed, c managed.ConnectionDetails) error {
panic("implement me")
}
func (m *Manager) UnpublishConnection(ctx context.Context, mg resource.Managed, c managed.ConnectionDetails) error {
// This resource didn't expose a connection secret.
p := mg.GetPublishConnectionDetailsTo()
if p == nil {
return nil
}
func (m *SecretStoreManager) UnpublishConnection(ctx context.Context, mg resource.Managed, c managed.ConnectionDetails) error {
panic("implement me")
ss, err := m.connectStore(ctx, p)
if err != nil {
return errors.Wrap(err, errConnectStore)
}
return errors.Wrap(ss.DeleteKeyValues(ctx, store.SecretInstance{
Name: p.Name,
Scope: mg.GetNamespace(),
Owner: meta.AsController(meta.TypedReferenceTo(mg, resource.MustGetKind(mg, m.typer))),
Metadata: p.Metadata,
}, store.KeyValues(c)), errDeleteFromStore)
}

View File

@ -1,11 +1,12 @@
package kubernetes
import (
"github.com/crossplane/crossplane-runtime/pkg/errors"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/crossplane/crossplane-runtime/pkg/errors"
)
// Error strings.

View File

@ -10,7 +10,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/secret"
"github.com/crossplane/crossplane-runtime/pkg/connection/secret/store"
"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource"
)
@ -30,22 +30,24 @@ type SecretStore struct {
applicator resource.Applicator
// remoteCluster will be used to decide whether to use owner references
remoteCluster bool
remoteCluster bool
defaultNamespace string
}
// NewSecretStore returns a new KubernetesSecretStore.
func NewSecretStore(ctx context.Context, local client.Client, cfg *v1.KubernetesSecretStoreConfig) (*SecretStore, error) {
if cfg == nil {
func NewSecretStore(ctx context.Context, local client.Client, cfg v1.SecretStoreConfig) (store.Store, error) {
if cfg.Kubernetes == nil {
// No KubernetesSecretStoreConfig provided, local API Server
// will be used as Secret Store.
return &SecretStore{
client: local,
applicator: resource.NewApplicatorWithRetry(resource.NewAPIPatchingApplicator(local),
resource.IsAPIErrorWrapped, nil),
defaultNamespace: cfg.DefaultScope,
}, nil
}
kfg, err := resource.CommonCredentialExtractor(ctx, cfg.Auth.Source, local, cfg.Auth.CommonCredentialSelectors)
kfg, err := resource.CommonCredentialExtractor(ctx, cfg.Kubernetes.Auth.Source, local, cfg.Kubernetes.Auth.CommonCredentialSelectors)
if err != nil {
return nil, errors.Wrap(err, errExtractKubernetesAuthCreds)
}
@ -58,16 +60,17 @@ func NewSecretStore(ctx context.Context, local client.Client, cfg *v1.Kubernetes
client: remote,
applicator: resource.NewApplicatorWithRetry(resource.NewAPIPatchingApplicator(remote),
resource.IsAPIErrorWrapped, nil),
remoteCluster: true,
defaultNamespace: cfg.DefaultScope,
remoteCluster: true,
}, nil
}
func (ss *SecretStore) ReadKeyValues(ctx context.Context, i secret.Instance) (secret.KeyValues, error) {
func (ss *SecretStore) ReadKeyValues(ctx context.Context, i store.SecretInstance) (store.KeyValues, error) {
s := &corev1.Secret{}
return s.Data, errors.Wrapf(ss.client.Get(ctx, types.NamespacedName{Name: i.Name, Namespace: i.Scope}, s), errGetSecret)
}
func (ss *SecretStore) WriteKeyValues(ctx context.Context, i secret.Instance, kv secret.KeyValues) error {
func (ss *SecretStore) WriteKeyValues(ctx context.Context, i store.SecretInstance, kv store.KeyValues) error {
s := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: i.Name,
@ -86,7 +89,7 @@ func (ss *SecretStore) WriteKeyValues(ctx context.Context, i secret.Instance, kv
return errors.Wrap(ss.applicator.Apply(ctx, s), errCreateOrUpdateSecret)
}
func (ss *SecretStore) DeleteKeyValues(ctx context.Context, i secret.Instance, kv secret.KeyValues) error {
func (ss *SecretStore) DeleteKeyValues(ctx context.Context, i store.SecretInstance, kv store.KeyValues) error {
s := &corev1.Secret{}
err := ss.client.Get(ctx, types.NamespacedName{Name: i.Name, Namespace: i.Scope}, s)
if kerrors.IsNotFound(err) {

View File

@ -0,0 +1,70 @@
package store
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
)
// An ConfigOption modifies an unstructured store config resource.
type ConfigOption func(*UnstructuredConfig)
// ConfigWithGroupVersionKind sets the GroupVersionKind of the unstructured
// store config resource.
func ConfigWithGroupVersionKind(gvk schema.GroupVersionKind) ConfigOption {
return func(c *UnstructuredConfig) {
c.SetGroupVersionKind(gvk)
}
}
// ConfigWithConditions returns an Option that sets the supplied conditions
// on an unstructured store config resource.
func ConfigWithConditions(c ...v1.Condition) ConfigOption {
return func(cr *UnstructuredConfig) {
cr.SetConditions(c...)
}
}
// NewConfig returns a new unstructured store config resource.
func NewConfig(opts ...ConfigOption) *UnstructuredConfig {
c := &UnstructuredConfig{unstructured.Unstructured{Object: make(map[string]interface{})}}
for _, f := range opts {
f(c)
}
return c
}
// An UnstructuredConfig is an unstructured store config resource.
type UnstructuredConfig struct {
unstructured.Unstructured
}
// GetCondition of this UnstructuredConfig resource.
func (c *UnstructuredConfig) GetCondition(ct v1.ConditionType) v1.Condition {
conditioned := v1.ConditionedStatus{}
// The path is directly `status` because conditions are inline.
if err := fieldpath.Pave(c.Object).GetValueInto("status", &conditioned); err != nil {
return v1.Condition{}
}
return conditioned.GetCondition(ct)
}
// SetConditions of this UnstructuredConfig resource.
func (c *UnstructuredConfig) SetConditions(conditions ...v1.Condition) {
conditioned := v1.ConditionedStatus{}
// The path is directly `status` because conditions are inline.
_ = fieldpath.Pave(c.Object).GetValueInto("status", &conditioned)
conditioned.SetConditions(conditions...)
_ = fieldpath.Pave(c.Object).SetValue("status.conditions", conditioned.Conditions)
}
// GetStoreConfig of this UnstructuredConfig resource.
func (c *UnstructuredConfig) GetStoreConfig() v1.SecretStoreConfig {
cfg := v1.SecretStoreConfig{}
if err := fieldpath.Pave(c.Object).GetValueInto("spec", &cfg); err != nil {
return v1.SecretStoreConfig{}
}
return cfg
}

View File

@ -1,4 +1,4 @@
package secret
package store
import (
"context"
@ -9,7 +9,8 @@ import (
)
type KeyValues map[string][]byte
type Instance struct {
type SecretInstance struct {
Name string
Scope string
Owner metav1.OwnerReference
@ -17,15 +18,15 @@ type Instance struct {
}
type KeyValuesReader interface {
ReadKeyValues(ctx context.Context, i Instance) (KeyValues, error)
ReadKeyValues(ctx context.Context, i SecretInstance) (KeyValues, error)
}
type KeyValuesWriter interface {
WriteKeyValues(ctx context.Context, i Instance, kv KeyValues) error
WriteKeyValues(ctx context.Context, i SecretInstance, kv KeyValues) error
}
type KeyValuesDeleter interface {
DeleteKeyValues(ctx context.Context, i Instance, kv KeyValues) error
DeleteKeyValues(ctx context.Context, i SecretInstance, kv KeyValues) error
}
type Store interface {

View File

@ -0,0 +1,28 @@
package vault
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection/secret/store"
)
type SecretStore struct{}
func NewSecretStore(ctx context.Context, local client.Client, cfg v1.SecretStoreConfig) (store.Store, error) {
return &SecretStore{}, nil
}
func (ss *SecretStore) ReadKeyValues(ctx context.Context, i store.SecretInstance) (store.KeyValues, error) {
panic("implement me")
}
func (ss *SecretStore) WriteKeyValues(ctx context.Context, i store.SecretInstance, kv store.KeyValues) error {
panic("implement me")
}
func (ss *SecretStore) DeleteKeyValues(ctx context.Context, i store.SecretInstance, kv store.KeyValues) error {
panic("implement me")
}

View File

@ -1,25 +0,0 @@
package vault
import (
"context"
"github.com/crossplane/crossplane-runtime/pkg/connection/secret"
)
type SecretStore struct{}
func NewSecretStore() *SecretStore {
return &SecretStore{}
}
func (ss *SecretStore) ReadKeyValues(ctx context.Context, i secret.Instance) (secret.KeyValues, error) {
panic("implement me")
}
func (ss *SecretStore) WriteKeyValues(ctx context.Context, i secret.Instance, kv secret.KeyValues) error {
panic("implement me")
}
func (ss *SecretStore) DeleteKeyValues(ctx context.Context, i secret.Instance, kv secret.KeyValues) error {
panic("implement me")
}