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

Namespace registry admin #462

Merged
merged 10 commits into from
Dec 20, 2023
6 changes: 3 additions & 3 deletions cmd/registry_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func registerANamespace(cmd *cobra.Command, args []string) {
}

// Parse the namespace URL to make sure it's okay
registrationEndpointURL, err := url.JoinPath(namespaceEndpoint, "api", "v1.0", "registry")
registrationEndpointURL, err := url.JoinPath(namespaceEndpoint, "api", "v2.0", "registry")
if err != nil {
log.Errorf("Failed to construction registration endpoint URL: %v", err)
}
Expand Down Expand Up @@ -143,7 +143,7 @@ func deleteANamespace(cmd *cobra.Command, args []string) {
os.Exit(1)
}

deletionEndpointURL, err := url.JoinPath(namespaceEndpoint, "api", "v1.0", "registry", prefix)
deletionEndpointURL, err := url.JoinPath(namespaceEndpoint, "api", "v2.0", "registry", prefix)
if err != nil {
log.Errorf("Failed to construction deletion endpoint URL: %v", err)
}
Expand All @@ -168,7 +168,7 @@ func listAllNamespaces(cmd *cobra.Command, args []string) {
os.Exit(1)
}

listEndpoint, err := url.JoinPath(namespaceEndpoint, "api", "v1.0", "registry")
listEndpoint, err := url.JoinPath(namespaceEndpoint, "api", "v2.0", "registry", "metadata")
if err != nil {
log.Errorf("Failed to construction list endpoint URL: %v", err)
}
Expand Down
12 changes: 10 additions & 2 deletions director/origin_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"encoding/json"
"net/http"
"net/url"
"path"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -58,6 +57,8 @@ type NamespaceCache interface {
var (
namespaceKeys = ttlcache.New[string, NamespaceCache](ttlcache.WithTTL[string, NamespaceCache](15 * time.Minute))
namespaceKeysMutex = sync.RWMutex{}

adminApprovalErr error
)

func CreateAdvertiseToken(namespace string) (string, error) {
Expand Down Expand Up @@ -148,6 +149,10 @@ func VerifyAdvertiseToken(token, namespace string) (bool, error) {
return false, errors.Wrap(err, "failed to marshal the public keyset into JWKS JSON")
}
log.Debugln("Constructed JWKS from fetching jwks:", string(jsonbuf))
if jsonbuf == nil {
adminApprovalErr = errors.New(namespace + " has not been approved by an administrator.")
return false, adminApprovalErr
}
}

if err != nil {
Expand Down Expand Up @@ -264,6 +269,9 @@ func GetRegistryIssuerURL(prefix string) (string, error) {
if err != nil {
return "", err
}
namespace_url.Path = path.Join(namespace_url.Path, "api", "v1.0", "registry", prefix, ".well-known", "issuer.jwks")
namespace_url.Path, err = url.JoinPath(namespace_url.Path, "api", "v2.0", "registry", "metadata", prefix, ".well-known", "issuer.jwks")
if err != nil {
return "", err
}
return namespace_url.String(), nil
}
6 changes: 3 additions & 3 deletions director/origin_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func TestVerifyAdvertiseToken(t *testing.T) {
kSet, err := config.GetIssuerPublicJWKS()
ar := MockCache{
GetFn: func(key string, keyset *jwk.Set) (jwk.Set, error) {
if key != "https://get-your-tokens.org/api/v1.0/registry/test-namespace/.well-known/issuer.jwks" {
t.Errorf("expecting: https://get-your-tokens.org/api/v1.0/registry/test-namespace/.well-known/issuer.jwks, got %q", key)
if key != "https://get-your-tokens.org/api/v2.0/registry/metadata/test-namespace/.well-known/issuer.jwks" {
t.Errorf("expecting: https://get-your-tokens.org/api/v2.0/registry/metadata/test-namespace/.well-known/issuer.jwks, got %q", key)
}
return *keyset, nil
},
Expand Down Expand Up @@ -160,7 +160,7 @@ func TestGetRegistryIssuerURL(t *testing.T) {
viper.Set("Federation.NamespaceURL", "test-path")
url, err = GetRegistryIssuerURL("test-prefix")
assert.Equal(t, nil, err)
assert.Equal(t, "test-path/api/v1.0/registry/test-prefix/.well-known/issuer.jwks", url)
assert.Equal(t, "test-path/api/v2.0/registry/metadata/test-prefix/.well-known/issuer.jwks", url)

}

Expand Down
27 changes: 16 additions & 11 deletions director/redirect.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,20 +363,20 @@ func ShortcutMiddleware(defaultResponse string) gin.HandlerFunc {
func registerServeAd(ctx *gin.Context, sType ServerType) {
tokens, present := ctx.Request.Header["Authorization"]
if !present || len(tokens) == 0 {
ctx.JSON(401, gin.H{"error": "Bearer token not present in the 'Authorization' header"})
ctx.JSON(http.StatusForbidden, gin.H{"error": "Bearer token not present in the 'Authorization' header"})
return
}

err := versionCompatCheck(ctx)
if err != nil {
log.Debugf("A version incompatibility was encountered while registering %s and no response was served: %v", sType, err)
ctx.JSON(500, gin.H{"error": "Incompatible versions detected: " + fmt.Sprintf("%v", err)})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Incompatible versions detected: " + fmt.Sprintf("%v", err)})
return
}

ad := OriginAdvertise{}
if ctx.ShouldBind(&ad) != nil {
ctx.JSON(400, gin.H{"error": "Invalid " + sType + " registration"})
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid " + sType + " registration"})
return
}

Expand All @@ -387,13 +387,13 @@ func registerServeAd(ctx *gin.Context, sType ServerType) {
ok, err := VerifyAdvertiseToken(token, namespace.Path)
if err != nil {
log.Warningln("Failed to verify token:", err)
ctx.JSON(400, gin.H{"error": "Authorization token verification failed"})
ctx.JSON(http.StatusForbidden, gin.H{"error": "Authorization token verification failed"})
return
}
if !ok {
log.Warningf("%s %v advertised to namespace %v without valid registration\n",
sType, ad.Name, namespace.Path)
ctx.JSON(400, gin.H{"error": sType + " not authorized to advertise to this namespace"})
ctx.JSON(http.StatusForbidden, gin.H{"error": sType + " not authorized to advertise to this namespace"})
return
}
}
Expand All @@ -402,29 +402,34 @@ func registerServeAd(ctx *gin.Context, sType ServerType) {
prefix := path.Join("caches", ad.Name)
ok, err := VerifyAdvertiseToken(token, prefix)
if err != nil {
log.Warningln("Failed to verify token:", err)
ctx.JSON(400, gin.H{"error": "Authorization token verification failed"})
if err == adminApprovalErr {
log.Warningln("Failed to verify token:", err)
ctx.JSON(http.StatusForbidden, gin.H{"error": "Cache is not admin approved"})
} else {
log.Warningln("Failed to verify token:", err)
ctx.JSON(http.StatusForbidden, gin.H{"error": "Authorization token verification failed"})
}
return
}
if !ok {
log.Warningf("%s %v advertised to namespace %v without valid registration\n",
sType, ad.Name, prefix)
ctx.JSON(400, gin.H{"error": sType + " not authorized to advertise to this namespace"})
ctx.JSON(http.StatusForbidden, gin.H{"error": sType + " not authorized to advertise to this namespace"})
return
}
}

ad_url, err := url.Parse(ad.URL)
if err != nil {
log.Warningf("Failed to parse %s URL %v: %v\n", sType, ad.URL, err)
ctx.JSON(400, gin.H{"error": "Invalid " + sType + " URL"})
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid " + sType + " URL"})
return
}

adWebUrl, err := url.Parse(ad.WebURL)
if err != nil && ad.WebURL != "" { // We allow empty WebURL string for backward compatibility
log.Warningf("Failed to parse origin Web URL %v: %v\n", ad.WebURL, err)
ctx.JSON(400, gin.H{"error": "Invalid origin Web URL"})
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid origin Web URL"})
return
}

Expand All @@ -449,7 +454,7 @@ func registerServeAd(ctx *gin.Context, sType ServerType) {
go PeriodicDirectorTest(ctx, sAd)
}

ctx.JSON(200, gin.H{"msg": "Successful registration"})
ctx.JSON(http.StatusOK, gin.H{"msg": "Successful registration"})
}

// Return a list of available origins URL in Prometheus HTTP SD format
Expand Down
6 changes: 3 additions & 3 deletions director/redirect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ func TestDirectorRegistration(t *testing.T) {
setupMockCache := func(t *testing.T, publicKey jwk.Key) MockCache {
return MockCache{
GetFn: func(key string, keyset *jwk.Set) (jwk.Set, error) {
if key != "https://get-your-tokens.org/api/v1.0/registry/foo/bar/.well-known/issuer.jwks" {
t.Errorf("expecting: https://get-your-tokens.org/api/v1.0/registry/foo/bar/.well-known/issuer.jwks, got %q", key)
if key != "https://get-your-tokens.org/api/v2.0/registry/metadata/foo/bar/.well-known/issuer.jwks" {
t.Errorf("expecting: https://get-your-tokens.org/api/v2.0/registry/metadata/foo/bar/.well-known/issuer.jwks, got %q", key)
}
return *keyset, nil
},
Expand Down Expand Up @@ -217,7 +217,7 @@ func TestDirectorRegistration(t *testing.T) {

r.ServeHTTP(w, c.Request)

assert.Equal(t, 400, w.Result().StatusCode, "Expected failing status code of 400")
assert.Equal(t, http.StatusForbidden, w.Result().StatusCode, "Expected failing status code of 403")
body, _ := io.ReadAll(w.Result().Body)
assert.Equal(t, `{"error":"Authorization token verification failed"}`, string(body), "Failure wasn't because token verification failed")

Expand Down
44 changes: 22 additions & 22 deletions registry/client_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestServeNamespaceRegistry(t *testing.T) {
require.NoError(t, err)

//Test functionality of registering a namespace (without identity)
err = NamespaceRegister(privKey, svr.URL+"/api/v1.0/registry", "", "/foo/bar")
err = NamespaceRegister(privKey, svr.URL+"/api/v2.0/registry", "", "/foo/bar")
require.NoError(t, err)

//Test we can list the namespace without an error
Expand All @@ -84,7 +84,7 @@ func TestServeNamespaceRegistry(t *testing.T) {
os.Stdout = w

//List the namespaces
err = NamespaceList(svr.URL + "/api/v1.0/registry")
err = NamespaceList(svr.URL + "/api/v2.0/registry")
require.NoError(t, err)
w.Close()
os.Stdout = oldStdout
Expand All @@ -103,7 +103,7 @@ func TestServeNamespaceRegistry(t *testing.T) {
r, w, _ := os.Pipe()
os.Stdout = w

err = NamespaceGet(svr.URL + "/api/v1.0/registry")
err = NamespaceGet(svr.URL + "/api/v2.0/registry")
require.NoError(t, err)
w.Close()
os.Stdout = oldStdout
Expand All @@ -116,13 +116,13 @@ func TestServeNamespaceRegistry(t *testing.T) {

t.Run("Test namespace delete", func(t *testing.T) {
//Test functionality of namespace delete
err = NamespaceDelete(svr.URL+"/api/v1.0/registry/foo/bar", "/foo/bar")
err = NamespaceDelete(svr.URL+"/api/v2.0/registry/foo/bar", "/foo/bar")
require.NoError(t, err)
var stdoutCapture string
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
err = NamespaceGet(svr.URL + "/api/v1.0/registry")
err = NamespaceGet(svr.URL + "/api/v2.0/registry")
require.NoError(t, err)
w.Close()
os.Stdout = oldStdout
Expand Down Expand Up @@ -161,19 +161,19 @@ func TestRegistryKeyChainingOSDF(t *testing.T) {
require.NoError(t, err)

// Start by registering /foo/bar with the default key
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo/bar")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo/bar")
require.NoError(t, err)

// Perform one test with a subspace and the same key -- should succeed
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo/bar/test")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo/bar/test")
require.NoError(t, err)

// For now, we simply don't allow further super/sub spacing of namespaces from topo, because how
// can we validate via a key if there is none?
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/topo/foo/bar")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/topo/foo/bar")
require.Error(t, err)

err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/topo")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/topo")
require.Error(t, err)

// Now we create a new key and try to use it to register a super/sub space. These shouldn't succeed
Expand All @@ -183,28 +183,28 @@ func TestRegistryKeyChainingOSDF(t *testing.T) {
privKey, err = config.GetIssuerPrivateJWK()
require.NoError(t, err)

err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo/bar/baz")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo/bar/baz")
require.ErrorContains(t, err, "Cannot register a namespace that is suffixed or prefixed")

err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo")
require.ErrorContains(t, err, "Cannot register a namespace that is suffixed or prefixed")

// Make sure we can register things similar but distinct in prefix and suffix
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/fo")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/fo")
require.NoError(t, err)
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo/barz")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo/barz")
require.NoError(t, err)

// Now turn off token chaining and retry -- no errors should occur
viper.Set("Registry.RequireKeyChaining", false)
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo/bar/baz")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo/bar/baz")
require.NoError(t, err)

err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo")
require.NoError(t, err)

// Finally, test with one value for topo
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/topo")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/topo")
require.NoError(t, err)

config.SetPreferredPrefix("pelican")
Expand All @@ -229,11 +229,11 @@ func TestRegistryKeyChaining(t *testing.T) {
require.NoError(t, err)

// Start by registering /foo/bar with the default key
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo/bar")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo/bar")
require.NoError(t, err)

// Perform one test with a subspace and the same key -- should succeed
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo/bar/test")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo/bar/test")
require.NoError(t, err)

// Now we create a new key and try to use it to register a super/sub space. These shouldn't succeed
Expand All @@ -243,18 +243,18 @@ func TestRegistryKeyChaining(t *testing.T) {
privKey, err = config.GetIssuerPrivateJWK()
require.NoError(t, err)

err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo/bar/baz")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo/bar/baz")
require.ErrorContains(t, err, "Cannot register a namespace that is suffixed or prefixed")

err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo")
require.ErrorContains(t, err, "Cannot register a namespace that is suffixed or prefixed")

// Now turn off token chaining and retry -- no errors should occur
viper.Set("Registry.RequireKeyChaining", false)
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo/bar/baz")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo/bar/baz")
require.NoError(t, err)

err = NamespaceRegister(privKey, registrySvr.URL+"/api/v1.0/registry", "", "/foo")
err = NamespaceRegister(privKey, registrySvr.URL+"/api/v2.0/registry", "", "/foo")
require.NoError(t, err)

viper.Reset()
Expand Down
Loading
Loading