Skip to content

Commit acf2c90

Browse files
authored
Controller can be configured to allow a predictable URL for tokens (#2371)
Fixes #2182
1 parent d1dffb1 commit acf2c90

File tree

4 files changed

+103
-1
lines changed

4 files changed

+103
-1
lines changed

internal/kube/grants/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type GrantConfig struct {
1515
Port int
1616
TlsCredentialsSecret string
1717
Hostname string
18+
RedeemByKey bool
1819
}
1920

2021
func BoundGrantConfig(flags *flag.FlagSet) (*GrantConfig, error) {
@@ -32,6 +33,9 @@ func BoundGrantConfig(flags *flag.FlagSet) (*GrantConfig, error) {
3233
}
3334
iflag.StringVar(flags, &c.TlsCredentialsSecret, "grant-server-tls-credentials", "SKUPPER_GRANT_SERVER_TLS_CREDENTIALS", "skupper-grant-server", "The name of a secret in which TLS credentials for the AccessGrant server are found.")
3435
iflag.StringVar(flags, &c.Hostname, "grant-server-podname", "HOSTNAME", "", "The name of the pod in which the AccessGrant server is running (defaults to $HOSTNAME).")
36+
if err := iflag.BoolVar(flags, &c.RedeemByKey, "allow-redeem-by-key", "SKUPPER_ALLOW_REDEEM_BY_KEY", false, "Allow AccessGrant redemption using a predictable key."); err != nil {
37+
errors = append(errors, err.Error())
38+
}
3539
if len(errors) > 0 {
3640
return c, fmt.Errorf("Invalid environment variable(s): %s", strings.Join(errors, ", "))
3741
}

internal/kube/grants/enabled.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ func enabled(controller *watchers.EventProcessor, currentNamespace string, watch
2929
}
3030
gc.autoConfigure = ac
3131
}
32+
if config.RedeemByKey {
33+
gc.grants.keyRedeem = true
34+
}
3235
return gc
3336
}
3437

internal/kube/grants/grant_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"errors"
7+
"fmt"
78
"io"
89
"net/http"
910
"net/http/httptest"
@@ -336,7 +337,83 @@ func Test_ServeHttp(t *testing.T) {
336337
assert.Equal(t, res.Code, tt.expectedCode)
337338
})
338339
}
340+
}
339341

342+
func Test_ServeHttpUIDAndKey(t *testing.T) {
343+
skupperObjects := []runtime.Object{
344+
&v2alpha1.AccessGrant{
345+
ObjectMeta: metav1.ObjectMeta{
346+
Name: "good",
347+
Namespace: "test",
348+
UID: "0bde3bc8-a4a2-404a-bfbe-44fdf7bf3231",
349+
},
350+
Spec: v2alpha1.AccessGrantSpec{
351+
RedemptionsAllowed: 3,
352+
Code: "supersecret",
353+
},
354+
Status: v2alpha1.AccessGrantStatus{
355+
Code: "supersecret",
356+
ExpirationTime: time.Date(2124, time.January, 0, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
357+
},
358+
},
359+
&v2alpha1.AccessGrant{
360+
ObjectMeta: metav1.ObjectMeta{
361+
Name: "not-bad",
362+
Namespace: "test",
363+
UID: "0bde3bc8-a4a2-404a-bfbe-44fdf7bf3232",
364+
},
365+
Spec: v2alpha1.AccessGrantSpec{
366+
RedemptionsAllowed: 3,
367+
Code: "supersecret",
368+
},
369+
Status: v2alpha1.AccessGrantStatus{
370+
Code: "supersecret",
371+
ExpirationTime: time.Date(2124, time.January, 0, 0, 0, 0, 0, time.UTC).Format(time.RFC3339),
372+
},
373+
},
374+
}
375+
scenarioKeys := []struct {
376+
valid []string
377+
invalid []string
378+
allowKeyRedeem bool
379+
}{
380+
{
381+
valid: []string{"0bde3bc8-a4a2-404a-bfbe-44fdf7bf3231", "0bde3bc8-a4a2-404a-bfbe-44fdf7bf3232"},
382+
invalid: []string{"test/good", "test/not-bad", "test/invalid", "really-invalid"},
383+
allowKeyRedeem: false,
384+
},
385+
{
386+
valid: []string{"0bde3bc8-a4a2-404a-bfbe-44fdf7bf3231", "0bde3bc8-a4a2-404a-bfbe-44fdf7bf3232", "test/good", "test/not-bad"},
387+
invalid: []string{"test/invalid", "really-invalid"},
388+
allowKeyRedeem: true,
389+
},
390+
}
391+
client, err := fake.NewFakeClient("test", nil, skupperObjects, "")
392+
if err != nil {
393+
t.Error(err)
394+
}
395+
registry := newGrants(client, dummyGenerator, "https", "")
396+
for _, obj := range skupperObjects {
397+
grant := obj.(*v2alpha1.AccessGrant)
398+
assert.Assert(t, registry.checkGrant(grant.Namespace+"/"+grant.Name, grant))
399+
}
400+
for _, scenario := range scenarioKeys {
401+
registry.keyRedeem = scenario.allowKeyRedeem
402+
for _, key := range scenario.valid {
403+
t.Run("redeem_valid_key_"+key, func(t *testing.T) {})
404+
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/%s", key), bytes.NewBufferString("supersecret"))
405+
res := httptest.NewRecorder()
406+
registry.ServeHTTP(res, req)
407+
assert.Equal(t, res.Code, http.StatusOK)
408+
}
409+
for _, key := range scenario.invalid {
410+
t.Run("redeem_invalid_key_"+key, func(t *testing.T) {})
411+
req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/%s", key), bytes.NewBufferString("supersecret"))
412+
res := httptest.NewRecorder()
413+
registry.ServeHTTP(res, req)
414+
assert.Equal(t, res.Code, http.StatusNotFound)
415+
}
416+
}
340417
}
341418

342419
type CheckGrantTestInvocation struct {

internal/kube/grants/grants.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Grants struct {
2828
scheme string
2929
grants map[kubetypes.UID]*skupperv2alpha1.AccessGrant
3030
grantIndex map[string]kubetypes.UID
31+
keyRedeem bool
3132
lock sync.Mutex
3233
logger *slog.Logger
3334
}
@@ -90,6 +91,11 @@ func (g *Grants) get(key string) *skupperv2alpha1.AccessGrant {
9091
g.lock.Lock()
9192
defer g.lock.Unlock()
9293

94+
if g.keyRedeem && strings.Contains(key, "/") {
95+
if uid, ok := g.grantIndex[key]; ok {
96+
key = string(uid)
97+
}
98+
}
9399
if grant, ok := g.grants[kubetypes.UID(key)]; ok {
94100
return grant
95101
}
@@ -302,7 +308,12 @@ func (g *Grants) ServeHTTP(w http.ResponseWriter, r *http.Request) {
302308
http.Error(w, "Only POST is supported", http.StatusMethodNotAllowed)
303309
return
304310
}
305-
key := strings.Join(strings.Split(r.URL.Path, "/"), "")
311+
var key string
312+
if namespaceName, ok := g.keyFromUrl(r.URL.Path); ok {
313+
key = namespaceName
314+
} else {
315+
key = strings.Join(strings.Split(r.URL.Path, "/"), "")
316+
}
306317
body, err := io.ReadAll(r.Body)
307318
if err != nil {
308319
g.logger.Error("Error reading body for path", slog.String("path", r.URL.Path), slog.Any("error", err))
@@ -338,6 +349,13 @@ func (g *Grants) ServeHTTP(w http.ResponseWriter, r *http.Request) {
338349
g.logger.Info("Redemption of access token succeeded", slog.String("namespace", grant.Namespace), slog.String("name", grant.Name))
339350
}
340351

352+
func (g *Grants) keyFromUrl(url string) (string, bool) {
353+
if g.keyRedeem && len(url) > 0 && strings.Contains(url[1:], "/") {
354+
return url[1:], true
355+
}
356+
return "", false
357+
}
358+
341359
type HttpError struct {
342360
text string
343361
code int

0 commit comments

Comments
 (0)