Skip to content

Commit fc3edfe

Browse files
authored
Add cluster-identity (#429)
* Add cluster/consumer ID * make namespaces names hash based Signed-off-by: Mangirdas Judeikis <mangirdas@judeikis.lt> On-behalf-of: @SAP mangirdas.judeikis@sap.com
1 parent 65b721c commit fc3edfe

37 files changed

Lines changed: 766 additions & 328 deletions

File tree

backend/auth/handler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func (ah *AuthHandler) HandleAuthorize(w http.ResponseWriter, r *http.Request) {
8282
SessionID: params.SessionID,
8383
ClusterID: params.ClusterID,
8484
ClientType: ClientType(params.ClientType),
85+
ConsumerID: params.ConsumerID,
8586
}
8687
}
8788

backend/auth/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type AuthorizeRequest struct {
5454
SessionID string `json:"session_id" form:"session_id"`
5555
ClusterID string `json:"cluster_id" form:"cluster_id"`
5656
ClientType ClientType `json:"client_type" form:"client_type"`
57+
ConsumerID string `json:"consumer_id" form:"consumer_id"`
5758
}
5859

5960
type CallbackRequest struct {

backend/client/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type ClientParameters struct {
2828
ClientSideRedirectURL string
2929
SessionID string
3030
ClientType string
31+
ConsumerID string
3132

3233
IsClusterScoped bool
3334
}
@@ -40,6 +41,7 @@ func GetQueryParams(r *http.Request) *ClientParameters {
4041
ClientSideRedirectURL: r.URL.Query().Get("client_side_redirect_url"),
4142
SessionID: r.URL.Query().Get("session_id"),
4243
ClientType: r.URL.Query().Get("client_type"),
44+
ConsumerID: r.URL.Query().Get("consumer_id"),
4345
}
4446
p.IsClusterScoped = p.ClusterID != ""
4547
return p
@@ -68,6 +70,9 @@ func (r *ClientParameters) WithParams(urlStr string) string {
6870
if r.ClientType != "" {
6971
query.Set("client_type", r.ClientType)
7072
}
73+
if r.ConsumerID != "" {
74+
query.Set("consumer_id", r.ConsumerID)
75+
}
7176

7277
parsedURL.RawQuery = query.Encode()
7378
return parsedURL.String()

backend/config.go

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ limitations under the License.
1717
package backend
1818

1919
import (
20-
"context"
2120
"fmt"
22-
"net/url"
23-
"strings"
2421

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

5047
Provider multicluster.Provider
5148
ExternalAddressGenerator kuberesources.ExternalAddressGeneratorFunc
52-
Manager mcmanager.Manager
53-
Scheme *runtime.Scheme
49+
50+
Manager mcmanager.Manager
51+
Scheme *runtime.Scheme
5452

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

109-
gen, err := newKCPExternalAddressGenerator(options.ExternalAddress)
107+
gen, err := kcpprovider.NewKCPExternalAddressGenerator(options.ExternalAddress)
110108
if err != nil {
111109
return nil, err
112110
}
@@ -137,44 +135,3 @@ func NewConfig(options *options.CompletedOptions) (*Config, error) {
137135

138136
return config, nil
139137
}
140-
141-
func newKCPExternalAddressGenerator(externalAddress string) (kuberesources.ExternalAddressGeneratorFunc, error) {
142-
var extURL *url.URL
143-
if externalAddress != "" {
144-
var err error
145-
146-
extURL, err = url.Parse(externalAddress)
147-
if err != nil {
148-
return nil, fmt.Errorf("invalid --external-address: %w", err)
149-
}
150-
}
151-
152-
return func(_ context.Context, clusterConfig *rest.Config) (string, error) {
153-
// In kcp case, we are talking via apiexport so clientconfig will be pointing to
154-
// https://192.168.2.166:6443/services/apiexport/root:org:ws/<apiexport-name>/clusters/2p0rtkf7b697s6mj
155-
// We need to extract host and /clusters/... part
156-
u, err := url.Parse(clusterConfig.Host)
157-
if err != nil {
158-
return "", err
159-
}
160-
161-
// Extract cluster ID from the path
162-
// Path format: /services/apiexport/root:org:ws/<apiexport-name>/clusters/{cluster-id}
163-
pathParts := strings.Split(strings.Trim(u.Path, "/"), "/")
164-
if len(pathParts) < 6 || pathParts[4] != "clusters" {
165-
return "", fmt.Errorf("invalid apiexport URL format")
166-
}
167-
168-
clusterID := pathParts[5]
169-
170-
// Construct new URL with cluster path
171-
var finalURL = u
172-
if extURL != nil {
173-
finalURL = extURL
174-
}
175-
176-
finalURL.Path = "/clusters/" + clusterID
177-
178-
return finalURL.String(), nil
179-
}, nil
180-
}

backend/http/handler.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -345,14 +345,6 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
345345
authCtx := auth.GetAuthContext(r.Context())
346346
state := authCtx.SessionState
347347

348-
kfg, err := h.kubeManager.HandleResources(r.Context(), state.Token.Subject+"#"+state.ClusterID, params.ClusterID)
349-
if err != nil {
350-
logger.Error(err, "failed to handle resources")
351-
statusCode, code, details := mapErrorToCode(err)
352-
writeErrorResponse(w, statusCode, code, "Failed to handle cluster resources", details)
353-
return
354-
}
355-
356348
// Parse JSON request body
357349
const maxBodySize = 1 << 20 // 1 MB
358350
r.Body = http.MaxBytesReader(w, r.Body, maxBodySize)
@@ -368,6 +360,21 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
368360
return
369361
}
370362

363+
// TODO: Move to validating admission.
364+
if bindRequest.ClusterIdentity.Identity == "" {
365+
logger.Error(fmt.Errorf("missing cluster identity"), "invalid bind request")
366+
writeErrorResponse(w, http.StatusBadRequest, kubebindv1alpha2.ErrorCodeBadRequest, "Missing cluster identity in bind request", "The cluster identity must be provided in the bind request")
367+
return
368+
}
369+
370+
kfg, err := h.kubeManager.HandleResources(r.Context(), state.Token.Subject, params.ConsumerID, params.ClusterID)
371+
if err != nil {
372+
logger.Error(err, "failed to handle resources")
373+
statusCode, code, details := mapErrorToCode(err)
374+
writeErrorResponse(w, statusCode, code, "Failed to handle cluster resources", details)
375+
return
376+
}
377+
371378
// Module consist of many resources and permissionClaims. Read it and translate to
372379
template, err := h.kubeManager.GetTemplates(r.Context(), params.ClusterID, bindRequest.TemplateRef.Name)
373380
if err != nil {

backend/kubernetes/manager.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ func NewKubernetesManager(
8383
return m, nil
8484
}
8585

86-
func (m *Manager) HandleResources(ctx context.Context, identity, cluster string) ([]byte, error) {
86+
func (m *Manager) HandleResources(
87+
ctx context.Context,
88+
author, identity, cluster string,
89+
) ([]byte, error) {
8790
logger := klog.FromContext(ctx).WithValues("identity", identity)
8891
ctx = klog.NewContext(ctx, logger)
8992

@@ -107,7 +110,7 @@ func (m *Manager) HandleResources(ctx context.Context, identity, cluster string)
107110
if len(nss.Items) == 1 {
108111
ns = nss.Items[0].Name
109112
} else {
110-
nsObj, err := kuberesources.CreateNamespace(ctx, c, m.namespacePrefix, identity)
113+
nsObj, err := kuberesources.CreateNamespace(ctx, c, m.namespacePrefix, identity, author)
111114
if err != nil {
112115
return nil, err
113116
}

backend/kubernetes/resources/namespace.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ package resources
1818

1919
import (
2020
"context"
21+
"crypto/sha256"
22+
"fmt"
2123
"strings"
2224

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

3033
const (
3134
IdentityAnnotationKey = "backend.kube-bind.io/identity"
35+
AuthorAnnotationKey = "backend.kube-bind.io/author"
3236
legacyIdentityAnnotationKey = "example-backend.kube-bind.io/identity"
3337
)
3438

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

56-
func CreateNamespace(ctx context.Context, client client.Client, generateName, id string) (*corev1.Namespace, error) {
57-
if !strings.HasSuffix(generateName, "-") {
58-
generateName += "-"
59-
}
60+
func CreateNamespace(ctx context.Context, client client.Client, generateNamePrefix, identity, author string) (*corev1.Namespace, error) {
61+
name := identityHash(identity)
6062
namespace := &corev1.Namespace{
6163
ObjectMeta: metav1.ObjectMeta{
62-
GenerateName: generateName,
64+
Name: fmt.Sprintf("%s-%s", generateNamePrefix, name),
6365
Annotations: map[string]string{
64-
IdentityAnnotationKey: id,
66+
IdentityAnnotationKey: identity,
67+
AuthorAnnotationKey: author,
6568
},
6669
},
6770
}
@@ -75,14 +78,19 @@ func CreateNamespace(ctx context.Context, client client.Client, generateName, id
7578
return nil, err
7679
}
7780

78-
if namespace.Annotations[IdentityAnnotationKey] != id && namespace.Annotations[legacyIdentityAnnotationKey] != id {
81+
if namespace.Annotations[IdentityAnnotationKey] != identity && namespace.Annotations[legacyIdentityAnnotationKey] != identity {
7982
return nil, errors.NewAlreadyExists(corev1.Resource("namespace"), namespace.Name)
8083
}
8184

82-
if err := handleLegacyAnnotations(ctx, client, namespace, id); err != nil {
85+
if err := handleLegacyAnnotations(ctx, client, namespace, identity); err != nil {
8386
return nil, err
8487
}
8588
}
8689

8790
return namespace, nil
8891
}
92+
93+
func identityHash(userName string) string {
94+
hash := sha256.Sum224([]byte(userName))
95+
return strings.ToLower(base36.EncodeBytes(hash[:8]))
96+
}

0 commit comments

Comments
 (0)