Hello guys I’m hitting /1.0/auth* endpoints but it is throwing an error Error: not found. Any help regarding this will save a lot of time. Thank you.
Hi,
There is no /1.0/auth
endpoint, as this is just a parent path for other endpoints, such as /1.0/auth/identities
or /1.0/auth/groups
.
lxc query /1.0/auth/groups
lxc query /1.0/auth/identities
Link to API spec: https://documentation.ubuntu.com/lxd/en/latest/api/
Also, which LXD version are you using? I think /auth*
endpoint is not available on LXD versions below 5.21.
Thank you for the quick response. I have implemented Google Sign-Up for user registration. After successful registration, I want to allow each user to create multiple projects in LXD. Additionally, a default project should be created automatically for every user upon registration. Can you please share the complete flow and best practices for implementing this, including utilizing LXD APIs to manage project creation for multiple users and their respective projects?
I want to give an option to user to create multiple projects.
Note: I have read all the apis. I’m able to create all the things but the management part is making it difficult to complete the step. I will be very thankful to you.
Hi @irtazahussain,
Generally speaking, the ability to create projects is a privilege that should only be granted to someone that you trust as root on the host machine. This is because a project can be created with restricted.containers.privilege=true
, which effectively grants root access to the LXD host machine. Therefore, I strongly recommend that you do not grant this privilege by default via social login on OIDC.
To create a default for each user on first login, you could do something like this:
package main
import (
"encoding/json"
"fmt"
"net/url"
"strings"
"github.com/canonical/lxd/client"
"github.com/canonical/lxd/shared/api"
"github.com/canonical/lxd/shared/entity"
"github.com/canonical/lxd/shared/logger"
)
func main() {
// Connect to LXD either via unix or HTTPS with a sufficiently privileged certificate.
// Deploy this however you choose.
lxdServer, err := lxd.ConnectLXDUnix("/var/snap/lxd/common/lxd/unix.socket", nil)
if err != nil {
panic(err)
}
// Get an event listener (websocket to /1.0/events).
listener, err := lxdServer.GetEventsAllProjects()
if err != nil {
panic(err)
}
// Listen for "identity-created" lifecycle events to create a new project for new OIDC users.
listener.AddHandler([]string{api.EventTypeLifecycle}, func(e api.Event) {
// Parse the lifecycle event.
var lifecycleEvent api.EventLifecycle
err := json.Unmarshal(e.Metadata, &lifecycleEvent)
if err != nil {
logger.Error("Failed to unmarshal lifecycle event", logger.Ctx{"err": err})
return
}
// Skip lifecycle events that do not relate to identity creation
if lifecycleEvent.Action != "identity-created" {
return
}
// Parse and validate event source.
u, err := url.Parse(lifecycleEvent.Source)
if err != nil {
logger.Error("Failed to parse lifecycle event source URL", logger.Ctx{"err": err})
return
}
entityType, _, _, pathArguments, err := entity.ParseURL(*u)
if err != nil {
logger.Error("Failed to parse lifecycle event source URL as a LXD entity", logger.Ctx{"err": err})
return
}
if entityType != entity.TypeIdentity {
logger.Error("Unexpected lifecycle event source URL entity type", logger.Ctx{"source_url": lifecycleEvent.Source, "expected_entity_type": entity.TypeIdentity, "found_entity_type": entityType})
return
}
if len(pathArguments) != 2 {
logger.Error("Unexpected number of path arguments in lifecycle event source URL", logger.Ctx{"source_url": lifecycleEvent.Source})
return
}
// Skip other identity types.
if pathArguments[0] != api.AuthenticationMethodOIDC {
return
}
// The email address is the second argument in the URL.
emailAddress := pathArguments[1]
// Replace any disallowed characters from the email address with hyphens to create a valid project name.
projectName := strings.Replace(emailAddress, "/", "-", -1)
projectName = strings.Replace(projectName, " ", "-", -1)
projectName = strings.Replace(projectName, "_", "-", -1)
projectName = strings.Replace(projectName, ".", "-", -1)
projectName = strings.Replace(projectName, `"`, "-", -1)
projectName = strings.Replace(projectName, "'", "-", -1)
// Construct an appropriate project for the user.
newProject := api.ProjectsPost{
Name: projectName,
ProjectPut: api.ProjectPut{
Description: fmt.Sprintf("Default project for %s", emailAddress),
Config: map[string]string{
// Set features so that the new user can create and modify networks in their new project.
// OVN networking must be enabled for this to work.
"features.networks": "true",
"features.networks.zones": "true",
// Set restricted to true
"restricted": "true",
// Optionally re-enable some restricted features by default for the new project.
// See https://documentation.ubuntu.com/lxd/en/latest/reference/projects/#project-restrictions
// For example, this option will allow users of the new project to use a network called `UPLINK`
// as the uplink network for OVN.
"restricted.networks.uplinks": "UPLINK",
// Set appropriate limits on the project to prevent overusage.
// This depends on your system, your expected number of projects/users and so on.
// See https://documentation.ubuntu.com/lxd/en/latest/reference/projects/#project-limits
// For example, this option limits the total amount of memory that the project may use.
"limits.memory": "8GiB",
},
},
}
// Create the project.
err = lxdServer.CreateProject(newProject)
if err != nil {
logger.Error("Failed to create a new project for user", logger.Ctx{"user": emailAddress, "err": err})
return
}
// Create a group for the user. Permissions cannot be directly granted to users, only groups.
groupName := projectName
err = lxdServer.CreateAuthGroup(api.AuthGroupsPost{
AuthGroupPost: api.AuthGroupPost{
Name: groupName,
},
AuthGroupPut: api.AuthGroupPut{
Description: fmt.Sprintf("Default group for %s", emailAddress),
Permissions: []api.Permission{
{
EntityType: string(entity.TypeProject),
// The operator permission grants access to everything in the project
// but the new user may not edit project configuration.
Entitlement: "operator",
EntityReference: entity.ProjectURL(projectName).String(),
},
},
},
})
if err != nil {
logger.Error("Failed to create a new group for user", logger.Ctx{"user": emailAddress, "err": err})
return
}
// Edit the new user to make them a member of the new group.
err = lxdServer.UpdateIdentity(api.AuthenticationMethodOIDC, emailAddress, api.IdentityPut{
Groups: []string{groupName},
}, "")
if err != nil {
logger.Error("Failed to add user to group", logger.Ctx{"user": emailAddress, "group": groupName, "err": err})
return
}
})
}
This is a Golang example. You could use PyLXD instead. You’ll also need something to restart this if an error occurs, e.g. systemd.
I am using REST APIs in my custom portal to interact with an LXD cluster.
Cluster configuration:
172.x.x.x, 172.x.x.x, 172.x.x.x
I want to create projects and handle tasks like self-service provisioning. How can I manage this scenario? I understand that certificate exchange is required to access the APIs. My users will be from (X), but I am using a general certificate to access the server. How can I retrieve the projects for a specific user (X) using REST APIs, considering the setup for each user?
LXD supports two methods of authentication, these are mutual TLS and OIDC.
If you want your users to sign up with Google, you can connect LXD to an OIDC provider that allows them to authenticate with the LXD server using their Google login (e.g. Auth0).
There are examples on how to create projects and restrict users to them in the documentation here: https://documentation.ubuntu.com/lxd/en/latest/howto/projects_confine/
We’ve self-hosted Authelia on our infrastructure. Can we perform all the necessary steps using Authelia? I’ve read the documentation, but I’m confused about the difference between a system user and a social user. For instance, in a cluster with many members, how will that user exist across other members?
I’m not familiar with Authelia but it seems it can be used as an OIDC provider. So you can configure LXD to use Authelia as an OIDC provider using these configuration keys.
When your users log in to LXD, they will be prompted to log in to Authelia. Once they have authenticated with Authelia, they will be authenticated with LXD. New OIDC users will not have any permissions and projects are not created automatically for them. However, when new users authenticate, LXD will emit a lifecycle event. Which is what the Go program I pasted above hooks into to meet your requirements.
I’m confused about the difference between a system user and a social user
There are no “system” or “social” users in LXD. Users are authenticated via mutual TLS (certificates), OIDC, or are using the unix socket (they are part of the lxd
group). Additionally, there is the multi-user daemon which can only be used locally (e.g. multiple users on the same machine). The multi-user daemon is not relevant to the HTTPS API.
For instance, in a cluster with many members, how will that user exist across other members?
LXD uses a distributed database to store the server configuration and state, including user information. See https://documentation.ubuntu.com/lxd/en/latest/database/
Thank you so much for making it super simple for everyone, especially me. Now my confusion is clear regarding everything related to working with LXD and managing compute and other aspects. You guys are very helpful to the community. Kudos!