Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kyma Binding with Gardener's Adminkubeconfig #1160

Merged
merged 17 commits into from
Sep 24, 2024
Merged
6 changes: 3 additions & 3 deletions cmd/broker/broker_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ func NewBrokerSuiteTestWithConfig(t *testing.T, cfg *Config, version ...string)
}
ts.poller = &broker.TimerPoller{PollInterval: 3 * time.Millisecond, PollTimeout: 3 * time.Second, Log: ts.t.Log}

ts.CreateAPI(inputFactory, cfg, db, provisioningQueue, deprovisioningQueue, updateQueue, logs, k8sClientProvider)
ts.CreateAPI(inputFactory, cfg, db, provisioningQueue, deprovisioningQueue, updateQueue, logs, k8sClientProvider, gardener.NewFakeClient())

notificationFakeClient := notification.NewFakeClient()
notificationBundleBuilder := notification.NewBundleBuilder(notificationFakeClient, cfg.Notification)
Expand Down Expand Up @@ -339,7 +339,7 @@ func (s *BrokerSuiteTest) CallAPI(method string, path string, body string) *http
return resp
}

func (s *BrokerSuiteTest) CreateAPI(inputFactory broker.PlanValidator, cfg *Config, db storage.BrokerStorage, provisioningQueue *process.Queue, deprovisionQueue *process.Queue, updateQueue *process.Queue, logs logrus.FieldLogger, skrK8sClientProvider *kubeconfig.FakeProvider) {
func (s *BrokerSuiteTest) CreateAPI(inputFactory broker.PlanValidator, cfg *Config, db storage.BrokerStorage, provisioningQueue *process.Queue, deprovisionQueue *process.Queue, updateQueue *process.Queue, logs logrus.FieldLogger, skrK8sClientProvider *kubeconfig.FakeProvider, gardenerClient client.Client) {
servicesConfig := map[string]broker.Service{
broker.KymaServiceName: {
Description: "",
Expand Down Expand Up @@ -368,7 +368,7 @@ func (s *BrokerSuiteTest) CreateAPI(inputFactory broker.PlanValidator, cfg *Conf
}
kcBuilder := &kcMock.KcBuilder{}
kcBuilder.On("Build", nil).Return("--kubeconfig file", nil)
createAPI(s.router, servicesConfig, inputFactory, cfg, db, provisioningQueue, deprovisionQueue, updateQueue, lager.NewLogger("api"), logs, planDefaults, kcBuilder, skrK8sClientProvider, skrK8sClientProvider)
createAPI(s.router, servicesConfig, inputFactory, cfg, db, provisioningQueue, deprovisionQueue, updateQueue, lager.NewLogger("api"), logs, planDefaults, kcBuilder, skrK8sClientProvider, skrK8sClientProvider, gardenerClient)

s.httpServer = httptest.NewServer(s.router)
}
Expand Down
11 changes: 8 additions & 3 deletions cmd/broker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"code.cloudfoundry.org/lager"
"github.com/dlmiddlecote/sqlstats"
shoot "github.com/gardener/gardener/pkg/apis/core/v1beta1"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/kyma-project/kyma-environment-broker/common/gardener"
Expand Down Expand Up @@ -212,6 +213,8 @@ func main() {
panicOnError(err)
err = imv1.AddToScheme(scheme.Scheme)
panicOnError(err)
err = shoot.AddToScheme(scheme.Scheme)
panicOnError(err)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down Expand Up @@ -289,6 +292,8 @@ func main() {
fatalOnError(err, logs)
dynamicGardener, err := dynamic.NewForConfig(gardenerClusterConfig)
fatalOnError(err, logs)
gardenerClient, err := initClient(gardenerClusterConfig)
fatalOnError(err, logs)

gardenerNamespace := fmt.Sprintf("garden-%v", cfg.Gardener.Project)
gardenerAccountPool := hyperscaler.NewAccountPool(dynamicGardener, gardenerNamespace)
Expand Down Expand Up @@ -345,7 +350,7 @@ func main() {

// create server
router := mux.NewRouter()
createAPI(router, servicesConfig, inputFactory, &cfg, db, provisionQueue, deprovisionQueue, updateQueue, logger, logs, inputFactory.GetPlanDefaults, kcBuilder, skrK8sClientProvider, skrK8sClientProvider)
createAPI(router, servicesConfig, inputFactory, &cfg, db, provisionQueue, deprovisionQueue, updateQueue, logger, logs, inputFactory.GetPlanDefaults, kcBuilder, skrK8sClientProvider, skrK8sClientProvider, gardenerClient)

// create metrics endpoint
router.Handle("/metrics", promhttp.Handler())
Expand Down Expand Up @@ -422,7 +427,7 @@ func logConfiguration(logs *logrus.Logger, cfg Config) {
logs.Infof("Is SubaccountMovementEnabled: %t", cfg.Broker.SubaccountMovementEnabled)
}

func createAPI(router *mux.Router, servicesConfig broker.ServicesConfig, planValidator broker.PlanValidator, cfg *Config, db storage.BrokerStorage, provisionQueue, deprovisionQueue, updateQueue *process.Queue, logger lager.Logger, logs logrus.FieldLogger, planDefaults broker.PlanDefaults, kcBuilder kubeconfig.KcBuilder, clientProvider K8sClientProvider, kubeconfigProvider KubeconfigProvider) {
func createAPI(router *mux.Router, servicesConfig broker.ServicesConfig, planValidator broker.PlanValidator, cfg *Config, db storage.BrokerStorage, provisionQueue, deprovisionQueue, updateQueue *process.Queue, logger lager.Logger, logs logrus.FieldLogger, planDefaults broker.PlanDefaults, kcBuilder kubeconfig.KcBuilder, clientProvider K8sClientProvider, kubeconfigProvider KubeconfigProvider, gardenerClient client.Client) {
suspensionCtxHandler := suspension.NewContextUpdateHandler(db.Operations(), provisionQueue, deprovisionQueue, logs)

defaultPlansConfig, err := servicesConfig.DefaultPlansConfig()
Expand Down Expand Up @@ -457,7 +462,7 @@ func createAPI(router *mux.Router, servicesConfig broker.ServicesConfig, planVal
planDefaults, logs, cfg.KymaDashboardConfig, kcBuilder, convergedCloudRegionProvider),
GetInstanceEndpoint: broker.NewGetInstance(cfg.Broker, db.Instances(), db.Operations(), kcBuilder, logs),
LastOperationEndpoint: broker.NewLastOperation(db.Operations(), db.InstancesArchived(), logs),
BindEndpoint: broker.NewBind(cfg.Broker.Binding, db.Instances(), logs, clientProvider, kubeconfigProvider, cfg.BindingTokenExpirationSeconds),
BindEndpoint: broker.NewBind(cfg.Broker.Binding, db.Instances(), logs, clientProvider, kubeconfigProvider, gardenerClient, cfg.BindingTokenExpirationSeconds),
UnbindEndpoint: broker.NewUnbind(logs),
GetBindingEndpoint: broker.NewGetBinding(logs),
LastBindingOperationEndpoint: broker.NewLastBindingOperation(logs),
Expand Down
19 changes: 0 additions & 19 deletions common/gardener/dynamic_fake.go

This file was deleted.

32 changes: 32 additions & 0 deletions common/gardener/fake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gardener

import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
dynamicFake "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func NewDynamicFakeClient(objects ...runtime.Object) *dynamicFake.FakeDynamicClient {
// dynamic fake client requirement https://github.com/kubernetes/client-go/issues/949#issuecomment-811192420
jaroslaw-pieszka marked this conversation as resolved.
Show resolved Hide resolved
scheme.Scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "Shoot"}, &unstructured.Unstructured{})
scheme.Scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "ShootList"}, &unstructured.UnstructuredList{})
scheme.Scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "SecretBinding"}, &unstructured.Unstructured{})
scheme.Scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "SecretBindingList"}, &unstructured.UnstructuredList{})

return dynamicFake.NewSimpleDynamicClient(scheme.Scheme, objects...)
}

func NewFakeClient(objects ...runtime.Object) client.Client {
scheme.Scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "Shoot"}, &unstructured.Unstructured{})
scheme.Scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "ShootList"}, &unstructured.UnstructuredList{})
scheme.Scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "SecretBinding"}, &unstructured.Unstructured{})
scheme.Scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "core.gardener.cloud", Version: "v1beta1", Kind: "SecretBindingList"}, &unstructured.UnstructuredList{})

return fake.NewClientBuilder().
WithRuntimeObjects(objects...).
Build()
}
36 changes: 30 additions & 6 deletions internal/broker/bind_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package broker

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
Expand All @@ -14,6 +15,7 @@ import (

"github.com/pivotal-cf/brokerapi/v8/domain"
"github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type BindingConfig struct {
Expand All @@ -25,14 +27,21 @@ type BindEndpoint struct {
config BindingConfig
instancesStorage storage.Instances

bindings broker.BindingsManager
tokenRequestsBindingManager broker.BindingsManager
gardenerBindingsManager broker.BindingsManager

log logrus.FieldLogger
}

func NewBind(cfg BindingConfig, instanceStorage storage.Instances, log logrus.FieldLogger, clientProvider broker.ClientProvider, kubeconfigProvider broker.KubeconfigProvider, tokenExpirationSeconds int) *BindEndpoint {
type BindingParams struct {
TokenRequests bool `json:"token_requests,omit"`
}

func NewBind(cfg BindingConfig, instanceStorage storage.Instances, log logrus.FieldLogger, clientProvider broker.ClientProvider, kubeconfigProvider broker.KubeconfigProvider, gardenerClient client.Client, tokenExpirationSeconds int) *BindEndpoint {
return &BindEndpoint{config: cfg, instancesStorage: instanceStorage, log: log.WithField("service", "BindEndpoint"),
bindings: broker.NewTokenRequestsBindingsManager(clientProvider, kubeconfigProvider, tokenExpirationSeconds)}
tokenRequestsBindingManager: broker.NewTokenRequestsBindingsManager(clientProvider, kubeconfigProvider, tokenExpirationSeconds),
gardenerBindingsManager: broker.NewGardenerBindingManager(gardenerClient, tokenExpirationSeconds),
}
}

type BindingData struct {
Expand Down Expand Up @@ -72,10 +81,25 @@ func (b *BindEndpoint) Bind(ctx context.Context, instanceID, bindingID string, d
).WithErrorKey("BindingNotSupported").Build()
}

// get kubeconfig for the instance
kubeconfig, err := b.bindings.Create(ctx, instance.RuntimeID, bindingID)
var parameters BindingParams
err = json.Unmarshal(details.RawParameters, &parameters)
if err != nil {
return domain.Binding{}, fmt.Errorf("failed to create binding: %s", err)
message := fmt.Sprintf("failed to unmarshal parameters: %s", err)
return domain.Binding{}, apiresponses.NewFailureResponse(fmt.Errorf(message), http.StatusInternalServerError, message)
}

var kubeconfig string
if parameters.TokenRequests {
// get kubeconfig for the instance
kubeconfig, err = b.tokenRequestsBindingManager.Create(ctx, instance, bindingID)
if err != nil {
return domain.Binding{}, fmt.Errorf("failed to create kyma binding using token requests: %s", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return domain.Binding{}, fmt.Errorf("failed to create kyma binding using token requests: %s", err)
return domain.Binding{}, fmt.Errorf("failed to create kyma binding using token request: %s", err)

}
} else {
kubeconfig, err = b.gardenerBindingsManager.Create(ctx, instance, bindingID)
if err != nil {
return domain.Binding{}, fmt.Errorf("failed to create kyma binding using adminkubeconfig gardener subresource: %s", err)
}
}

return domain.Binding{
Expand Down
19 changes: 13 additions & 6 deletions internal/broker/bind_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestCreateBindingEndpoint(t *testing.T) {
//// populate skr client with data
err = skrClient.Create(context.Background(), &corev1.ServiceAccount{
ObjectMeta: v1.ObjectMeta{
Name: "admin",
Name: "default",
Namespace: "default",
},
})
Expand All @@ -97,7 +97,7 @@ func TestCreateBindingEndpoint(t *testing.T) {
// this is ok because we know exactly how we want to be serialized
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "ClusterRole"},
ObjectMeta: metav1.ObjectMeta{
Name: "admin-all",
Name: "default-all",
},
Rules: []rbacv1.PolicyRule{
{
Expand All @@ -113,18 +113,18 @@ func TestCreateBindingEndpoint(t *testing.T) {
err = skrClient.Create(context.Background(), &rbacv1.ClusterRoleBinding{
TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "ClusterRoleBinding"},
ObjectMeta: metav1.ObjectMeta{
Name: "admin",
Name: "default-default-all",
},
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: "admin-all",
Name: "default-all",
},
Subjects: []rbacv1.Subject{
{
Kind: rbacv1.ServiceAccountKind,
Namespace: "default",
Name: "admin",
Name: "default",
},
},
})
Expand Down Expand Up @@ -156,6 +156,12 @@ func TestCreateBindingEndpoint(t *testing.T) {
}...).
Build()

//// create fake kubernetes client - kcp
gardenerClient := fake.NewClientBuilder().
WithScheme(sch).
WithRuntimeObjects([]runtime.Object{}...).
Build()

//// database
db := storage.NewMemoryStorage()
err = db.Instances().Insert(fixture.FixInstance("1"))
Expand All @@ -172,7 +178,7 @@ func TestCreateBindingEndpoint(t *testing.T) {
}

//// api handler
bindEndpoint := NewBind(*bindingCfg, db.Instances(), logs, skrK8sClientProvider, skrK8sClientProvider, 10000)
bindEndpoint := NewBind(*bindingCfg, db.Instances(), logs, skrK8sClientProvider, skrK8sClientProvider, gardenerClient, 10000)
apiHandler := handlers.NewApiHandler(KymaEnvironmentBroker{
nil,
nil,
Expand Down Expand Up @@ -200,6 +206,7 @@ func TestCreateBindingEndpoint(t *testing.T) {
"service_id": "123",
"plan_id": "%s",
"parameters": {
"token_requests": true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"token_requests": true
"token_request": true

}
}`, fixture.PlanId), t)

Expand Down
13 changes: 7 additions & 6 deletions internal/broker/bindings/bindings_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"

"github.com/kyma-project/kyma-environment-broker/internal"
"github.com/kyma-project/kyma-environment-broker/internal/kubeconfig"
"github.com/kyma-project/kyma-environment-broker/internal/ptr"
authv1 "k8s.io/api/authentication/v1"
Expand All @@ -15,7 +16,7 @@ type Credentials struct {
}

type BindingsManager interface {
Create(ctx context.Context, runtimeID, bindingID string) (string, error)
Create(ctx context.Context, instance *internal.Instance, bindingID string) (string, error)
}

type ClientProvider interface {
Expand All @@ -40,16 +41,16 @@ func NewTokenRequestsBindingsManager(clientProvider ClientProvider, kubeconfigPr
}
}

func (c *TokenRequestsBindingsManager) Create(ctx context.Context, runtimeID, bindingID string) (string, error) {
clientset, err := c.clientProvider.K8sClientSetForRuntimeID(runtimeID)
func (c *TokenRequestsBindingsManager) Create(ctx context.Context, instance *internal.Instance, bindingID string) (string, error) {
jaroslaw-pieszka marked this conversation as resolved.
Show resolved Hide resolved
clientset, err := c.clientProvider.K8sClientSetForRuntimeID(instance.RuntimeID)

if err != nil {
return "", fmt.Errorf("while creating a runtime client for binding creation: %v", err)
}

tokenRequest := &authv1.TokenRequest{
ObjectMeta: mv1.ObjectMeta{
Name: "admin",
Name: "default",
Namespace: "default",
},
Spec: authv1.TokenRequestSpec{
Expand All @@ -58,13 +59,13 @@ func (c *TokenRequestsBindingsManager) Create(ctx context.Context, runtimeID, bi
}

// old usage with client.Client
tkn, err := clientset.CoreV1().ServiceAccounts("default").CreateToken(ctx, "admin", tokenRequest, mv1.CreateOptions{})
tkn, err := clientset.CoreV1().ServiceAccounts("default").CreateToken(ctx, "default", tokenRequest, mv1.CreateOptions{})

if err != nil {
return "", fmt.Errorf("while creating a token request: %v", err)
}

kubeconfigContent, err := c.kubeconfigBuilder.BuildFromAdminKubeconfigForBinding(runtimeID, tkn.Status.Token)
kubeconfigContent, err := c.kubeconfigBuilder.BuildFromAdminKubeconfigForBinding(instance.RuntimeID, tkn.Status.Token)

if err != nil {
return "", fmt.Errorf("while creating a kubeconfig: %v", err)
Expand Down
3 changes: 2 additions & 1 deletion internal/broker/bindings/bindings_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"
"time"

"github.com/kyma-project/kyma-environment-broker/internal"
"github.com/kyma-project/kyma-environment-broker/internal/kubeconfig"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/util/wait"
Expand Down Expand Up @@ -43,7 +44,7 @@ func TestCredentialsManagerImpl_Create(t *testing.T) {
// }

// Act
kubeconfig, err := manager.Create(ctx, runtimeID, bindingID)
kubeconfig, err := manager.Create(ctx, &internal.Instance{RuntimeID: runtimeID}, bindingID)

// Assert
require.NoError(t, err)
Expand Down
54 changes: 54 additions & 0 deletions internal/broker/bindings/gardener_request_bindingmanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package broker

import (
"context"
"fmt"
"time"

authenticationv1alpha1 "github.com/gardener/gardener/pkg/apis/authentication/v1alpha1"
shoot "github.com/gardener/gardener/pkg/apis/core/v1beta1"
"github.com/kyma-project/kyma-environment-broker/internal"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type GardenerBindingManager struct {
tokenExpiration int
jaroslaw-pieszka marked this conversation as resolved.
Show resolved Hide resolved
gardenerClient client.Client
}

func NewGardenerBindingManager(gardenerClient client.Client, tokenExpiration int) *GardenerBindingManager {
return &GardenerBindingManager{
gardenerClient: gardenerClient,
tokenExpiration: tokenExpiration,
}
}

func (c *GardenerBindingManager) Create(ctx context.Context, instance *internal.Instance, bindingID string) (string, error) {

shoot := &shoot.Shoot{
TypeMeta: metav1.TypeMeta{APIVersion: "core.gardener.cloud/v1beta1", Kind: "Shoot"},
}
err := c.gardenerClient.Get(context.Background(), client.ObjectKey{Name: instance.InstanceDetails.ShootName, Namespace: "garden-kyma-dev"}, shoot)
if err != nil {
return "", fmt.Errorf("while getting shoot: %v", err)
}

fmt.Println(shoot)
jaroslaw-pieszka marked this conversation as resolved.
Show resolved Hide resolved
expiration := 10 * time.Minute
jaroslaw-pieszka marked this conversation as resolved.
Show resolved Hide resolved
expirationSeconds := int64(expiration.Seconds())
jaroslaw-pieszka marked this conversation as resolved.
Show resolved Hide resolved

adminKubeconfigRequest := &authenticationv1alpha1.AdminKubeconfigRequest{
Spec: authenticationv1alpha1.AdminKubeconfigRequestSpec{
ExpirationSeconds: &expirationSeconds,
},
}

err = c.gardenerClient.SubResource("adminkubeconfig").Create(context.Background(), shoot, adminKubeconfigRequest)
if err != nil {
return "", fmt.Errorf("while creating admin kubeconfig request: %v", err)
}
shootKubeconfig := adminKubeconfigRequest.Status.Kubeconfig

return string(shootKubeconfig), nil
}
Loading