Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/auth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func (ah *AuthHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request) {
SessionID: params.SessionID,
ClusterID: params.ClusterID,
ClientType: ClientType(params.ClientType),
ConsumerID: params.ConsumerID,
}
}

Expand Down
1 change: 1 addition & 0 deletions backend/auth/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type AuthorizeRequest struct {
SessionID string `json:"session_id" form:"session_id"`
ClusterID string `json:"cluster_id" form:"cluster_id"`
ClientType ClientType `json:"client_type" form:"client_type"`
ConsumerID string `json:"consumer_id" form:"consumer_id"`
}

type CallbackRequest struct {
Expand Down
5 changes: 5 additions & 0 deletions backend/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type ClientParameters struct {
ClientSideRedirectURL string
SessionID string
ClientType string
ConsumerID string

IsClusterScoped bool
}
Expand All @@ -40,6 +41,7 @@ func GetQueryParams(r *http.Request) *ClientParameters {
ClientSideRedirectURL: r.URL.Query().Get("client_side_redirect_url"),
SessionID: r.URL.Query().Get("session_id"),
ClientType: r.URL.Query().Get("client_type"),
ConsumerID: r.URL.Query().Get("consumer_id"),
}
p.IsClusterScoped = p.ClusterID != ""
return p
Expand Down Expand Up @@ -68,6 +70,9 @@ func (r *ClientParameters) WithParams(urlStr string) string {
if r.ClientType != "" {
query.Set("client_type", r.ClientType)
}
if r.ConsumerID != "" {
query.Set("consumer_id", r.ConsumerID)
}

parsedURL.RawQuery = query.Encode()
return parsedURL.String()
Expand Down
51 changes: 4 additions & 47 deletions backend/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ limitations under the License.
package backend

import (
"context"
"fmt"
"net/url"
"strings"

"github.com/kcp-dev/multicluster-provider/apiexport"
apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1"
Expand Down Expand Up @@ -49,8 +46,9 @@ type Config struct {

Provider multicluster.Provider
ExternalAddressGenerator kuberesources.ExternalAddressGeneratorFunc
Manager mcmanager.Manager
Scheme *runtime.Scheme

Manager mcmanager.Manager
Scheme *runtime.Scheme

ClientConfig *rest.Config
}
Expand Down Expand Up @@ -106,7 +104,7 @@ func NewConfig(options *options.CompletedOptions) (*Config, error) {
return nil, fmt.Errorf("error setting up kcp provider: %w", err)
}

gen, err := newKCPExternalAddressGenerator(options.ExternalAddress)
gen, err := kcpprovider.NewKCPExternalAddressGenerator(options.ExternalAddress)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -137,44 +135,3 @@ func NewConfig(options *options.CompletedOptions) (*Config, error) {

return config, nil
}

func newKCPExternalAddressGenerator(externalAddress string) (kuberesources.ExternalAddressGeneratorFunc, error) {
var extURL *url.URL
if externalAddress != "" {
var err error

extURL, err = url.Parse(externalAddress)
if err != nil {
return nil, fmt.Errorf("invalid --external-address: %w", err)
}
}

return func(_ context.Context, clusterConfig *rest.Config) (string, error) {
// In kcp case, we are talking via apiexport so clientconfig will be pointing to
// https://192.168.2.166:6443/services/apiexport/root:org:ws/<apiexport-name>/clusters/2p0rtkf7b697s6mj
// We need to extract host and /clusters/... part
u, err := url.Parse(clusterConfig.Host)
if err != nil {
return "", err
}

// Extract cluster ID from the path
// Path format: /services/apiexport/root:org:ws/<apiexport-name>/clusters/{cluster-id}
pathParts := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(pathParts) < 6 || pathParts[4] != "clusters" {
return "", fmt.Errorf("invalid apiexport URL format")
}

clusterID := pathParts[5]

// Construct new URL with cluster path
var finalURL = u
if extURL != nil {
finalURL = extURL
}

finalURL.Path = "/clusters/" + clusterID

return finalURL.String(), nil
}, nil
}
23 changes: 15 additions & 8 deletions backend/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,14 +345,6 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
authCtx := auth.GetAuthContext(r.Context())
state := authCtx.SessionState

kfg, err := h.kubeManager.HandleResources(r.Context(), state.Token.Subject+"#"+state.ClusterID, params.ClusterID)
if err != nil {
logger.Error(err, "failed to handle resources")
statusCode, code, details := mapErrorToCode(err)
writeErrorResponse(w, statusCode, code, "Failed to handle cluster resources", details)
return
}

// Parse JSON request body
const maxBodySize = 1 << 20 // 1 MB
r.Body = http.MaxBytesReader(w, r.Body, maxBodySize)
Expand All @@ -368,6 +360,21 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
return
}

// TODO: Move to validating admission.
if bindRequest.ClusterIdentity.Identity == "" {
logger.Error(fmt.Errorf("missing cluster identity"), "invalid bind request")
writeErrorResponse(w, http.StatusBadRequest, kubebindv1alpha2.ErrorCodeBadRequest, "Missing cluster identity in bind request", "The cluster identity must be provided in the bind request")
return
}

kfg, err := h.kubeManager.HandleResources(r.Context(), state.Token.Subject, params.ConsumerID, params.ClusterID)
if err != nil {
logger.Error(err, "failed to handle resources")
statusCode, code, details := mapErrorToCode(err)
writeErrorResponse(w, statusCode, code, "Failed to handle cluster resources", details)
return
}

// Module consist of many resources and permissionClaims. Read it and translate to
template, err := h.kubeManager.GetTemplates(r.Context(), params.ClusterID, bindRequest.TemplateRef.Name)
if err != nil {
Expand Down
7 changes: 5 additions & 2 deletions backend/kubernetes/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ func NewKubernetesManager(
return m, nil
}

func (m *Manager) HandleResources(ctx context.Context, identity, cluster string) ([]byte, error) {
func (m *Manager) HandleResources(
ctx context.Context,
author, identity, cluster string,
) ([]byte, error) {
logger := klog.FromContext(ctx).WithValues("identity", identity)
ctx = klog.NewContext(ctx, logger)

Expand All @@ -107,7 +110,7 @@ func (m *Manager) HandleResources(ctx context.Context, identity, cluster string)
if len(nss.Items) == 1 {
ns = nss.Items[0].Name
} else {
nsObj, err := kuberesources.CreateNamespace(ctx, c, m.namespacePrefix, identity)
nsObj, err := kuberesources.CreateNamespace(ctx, c, m.namespacePrefix, identity, author)
if err != nil {
return nil, err
}
Expand Down
24 changes: 16 additions & 8 deletions backend/kubernetes/resources/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ package resources

import (
"context"
"crypto/sha256"
"fmt"
"strings"

"github.com/martinlindhe/base36"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -29,6 +32,7 @@ import (

const (
IdentityAnnotationKey = "backend.kube-bind.io/identity"
AuthorAnnotationKey = "backend.kube-bind.io/author"
legacyIdentityAnnotationKey = "example-backend.kube-bind.io/identity"
)

Expand All @@ -53,15 +57,14 @@ func handleLegacyAnnotations(ctx context.Context, cl client.Client, namespace *c
return nil
}

func CreateNamespace(ctx context.Context, client client.Client, generateName, id string) (*corev1.Namespace, error) {
if !strings.HasSuffix(generateName, "-") {
generateName += "-"
}
func CreateNamespace(ctx context.Context, client client.Client, generateNamePrefix, identity, author string) (*corev1.Namespace, error) {
name := identityHash(identity)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: generateName,
Name: fmt.Sprintf("%s-%s", generateNamePrefix, name),
Annotations: map[string]string{
IdentityAnnotationKey: id,
IdentityAnnotationKey: identity,
AuthorAnnotationKey: author,
},
},
}
Expand All @@ -75,14 +78,19 @@ func CreateNamespace(ctx context.Context, client client.Client, generateName, id
return nil, err
}

if namespace.Annotations[IdentityAnnotationKey] != id && namespace.Annotations[legacyIdentityAnnotationKey] != id {
if namespace.Annotations[IdentityAnnotationKey] != identity && namespace.Annotations[legacyIdentityAnnotationKey] != identity {
return nil, errors.NewAlreadyExists(corev1.Resource("namespace"), namespace.Name)
}

if err := handleLegacyAnnotations(ctx, client, namespace, id); err != nil {
if err := handleLegacyAnnotations(ctx, client, namespace, identity); err != nil {
return nil, err
}
}

return namespace, nil
}

func identityHash(userName string) string {
hash := sha256.Sum224([]byte(userName))
return strings.ToLower(base36.EncodeBytes(hash[:8]))
}
Loading