Hello Ory! We want to use Keto namespaces for mult...
# talk-keto
c
Hello Ory! We want to use Keto namespaces for multi-tenancy, ie. 1 namespace per tenant. The reason for this is that a single user can access separate tenants, and we want a different set of permissions within each of them. Namespacing seems brilliant for this purpose, however it brings me to a relevant question: Is there a way to create namespaces programmatically? So when a new tenant is registered, we automatically create their namespace, and also probably grant the creator a set of permissions :)
m
Hello HC πŸ™‚ How are you registering new tenants ?
c
From a customers pov, its a page where you enter some information and click a create-button - from a technical point of view, it's a grpc-call to a microservice that emits an event to a bunch of other microservices, which in turn prepare everything for the new tenant, like run db migrations, creates a profile, adds the first user to the tenant etc. 😁
By tenant, I am in this case not talking about Azure AD tenants or other SSO providers tenants, but the general concept of a tenant, which we use to separate customers data, settings, layout etc. πŸ™‚
m
So you are looking for an API to create namespaces basically? See this discussion: https://ory-community.slack.com/archives/C012RBZFMDG/p1667384554847369 let me know if that clears it up πŸ™‚
c
Ah, yeah, the thought actually struck me, but it felt a bit hacky πŸ˜›
We currently use the ory cli to replace the config file in ory network, do you know if the ory sdk has a similar method/api available? That seems less hacky than shipping a docker image with the ory cli installed, and making command line calls during runtime πŸ™‚
c
Cool, thanks! All I need to think about then is concurrency, but that's not your concern πŸ™‚. I'll try to solve it using the ApiClient then!
m
sounds good! feel free to share some feedback how it went with us, I would be interested πŸ™
c
Sure thing, friend πŸ˜„
Alrite, @magnificent-energy-493, I have written a wrapper now that should theoretically be able to update namespaces, but I'm stuck getting 404s. My educated guess is that the personal access token (issued by ory network, through the gui) does not have access to the ProjectApi. If this is correct, which I kinda assume it is: What credentials can I use in the SDK to programmatically access the ProjectApi?
Specifically, the endpoints (methods) I'm using are
APIClient.ProjectApi.GetProject
and
APIClient.ProjectApi.PatchProject
m
Hello HC, I am so sorry it seems this endpoint is not usable via the SDK yet 😞 , I see to find out how you can use it directly
c
Alrite, thanks! If I get some insight into how the endpoint behaves under the hood, I can probably add some magic to our implementation of the interface 😁
n
Hi HC, you could look at our CLI for an example of how to update the namespace configuration: https://github.com/ory/cli/blob/master/cmd/cloudx/project/update_namespace_config.go#L44-L55. The gist is that the
namespaces.location
key of the config should contain a base64 URL of the namespace configuration you want to write, as
base64://<base64-encoded data>
.
s
The rule is probably not matching because you have to use a different host
api.console.ory.sh
, not your slug host.
c
Ooh, I see, yeah you are right, I am using my slug host. I'll see what happens if I use your public fqdn - if there are still issues, I'll implement Hennings solution! Thanks guys 😎
Ah, now I get the expected 401, which I guess makes sense, since my personal access key is for the specific project indicated by the slug. Is there a way I can create some valid (ideally long-lived) credentials for api.console.ory.sh?
m
Hey HC, I was able to clarify it further: You most likely have to use the cookie you get when you sign in to the console you can copy the cookie out from the browser and use it inside a curl request It’s a bit hacky, but it should work
Can you try that and let me know if it works πŸ™
c
Haha, it's exactly what I was doing as we speak - it seems I can't just append the token to the context though, like the personal access key, but rather I need to initiate an actual session. I'll try to replicate what your cli does inside my own service πŸ™‚
Thanks, man!
m
Sure happy to help, and apologies for the somewhat convoluted way to call this endpoint atm. We have plans to improve this soon.
c
Alrite, I fetched my own credentials and added them to every request, using
Copy code
c.AddDefaultHeader("Authorization", "Bearer "+sessionToken)
Hacky, I know, but it was easier than having to replace the entire http client 😎
Also, the session token will probably time out after a while, 72 hours or something? So I'll have to get some mechanism in place to refresh it every once in a while, probably
Alrite, it works nicely now, although the config is a bit messy, I managed to replicate roughly how the CLI handles it πŸ™‚
An absolute hack, but:
Copy code
func NewOryNamespaceClient(url *url.URL, user, password, projectId string, httpClient http.Client) NamespaceClient {
	c := ory.NewConfiguration()
	kratosUrl, _ := url.Parse("")
	kratosUrl.Host = "project." + kratosUrl.Host
	url.Host = "api." + url.Host
	c.Servers = ory.ServerConfigurations{{URL: url.String()}}
	c.HTTPClient = httpClient.(*gohttp.Client)
	c.OperationServers["FrontendApiService.CreateNativeLoginFlow"] = ory.ServerConfigurations{{URL: kratosUrl.String()}}
	c.OperationServers["FrontendApiService.UpdateLoginFlow"] = ory.ServerConfigurations{{URL: kratosUrl.String()}}

	return &oryClient{
		ory.NewAPIClient(c),
		"",
		&oryNamespacesContext{
			user:      user,
			password:  password,
			projectId: projectId,
		},
	}
}
is how I set up the client.
Copy code
func (oc *oryClient) authenticate(ctx context.Context) error {
	flow, _, err := oc.FrontendApi.CreateNativeLoginFlow(ctx).Execute()
	if err != nil {
		return err
	}
	login, _, err := oc.FrontendApi.UpdateLoginFlow(ctx).Flow(flow.Id).UpdateLoginFlowBody(ory.UpdateLoginFlowBody{
		UpdateLoginFlowWithPasswordMethod: ory.NewUpdateLoginFlowWithPasswordMethod(oc.nsContext.user, "password", oc.nsContext.password),
	}).Execute()
	if err != nil {
		return err
	}
	oc.token = *login.SessionToken

	return nil
}
is how I authenticate, and
Copy code
func (oc *oryClient) UpdateNamespaces(ctx context.Context, namespaces map[string]int) (*ory.SuccessfulProjectUpdate, error) {
	out := make([]Namespace, len(namespaces))
	var i int
	for name, id := range namespaces {
		out[i] = Namespace{
			Id:   id,
			Name: name,
		}
		i++
	}
	patch := []ory.JsonPatch{
		{
			Op:   OryOpReplace,
			Path: "/services/permission/config/" + OryNamespacesKey,
			// We may have to encode something here, to like json or yaml, who knows?
			Value: out,
		},
	}
	success, res, err := oc.patchProject(ctx, patch)
	if res.StatusCode == gohttp.StatusUnauthorized {
		if err := oc.authenticate(ctx); err != nil {
			return nil, err
		}
		success, _, err = oc.patchProject(ctx, patch)
	}

	return success, err
}
Is how I update namespaces. The logic of appending new namespaces to the existing list is handled outside of my ory client wrapper πŸ™‚.
m
Oh interesting, thanks for sharing!
231 Views