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

Auth: Allow listing unmanaged networks with fine-grained auth #14447

Merged
merged 7 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/metadata.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6331,6 +6331,9 @@ container or containers that use it. This allows using the `zfs` command in the
`can_view_warnings`
: Grants permission to view warnings.

`can_view_unmanaged_networks`
: Grants permission to view unmanaged networks on the LXD host machines.


<!-- entity group server end -->
<!-- entity group storage_bucket start -->
Expand Down
3 changes: 3 additions & 0 deletions lxd/auth/drivers/openfga_model.openfga
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ type server

# Grants permission to view warnings.
define can_view_warnings: [identity, service_account, group#member] or admin or viewer

# Grants permission to view unmanaged networks on the LXD host machines.
define can_view_unmanaged_networks: [identity, service_account, group#member] or admin or viewer
type certificate
relations
define server: [server]
Expand Down
2 changes: 1 addition & 1 deletion lxd/auth/drivers/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func (t *tls) allowProjectUnspecificEntityType(entitlement auth.Entitlement, ent
switch entityType {
case entity.TypeServer:
// Restricted TLS certificates have the following entitlements on server.
return shared.ValueInSlice(entitlement, []auth.Entitlement{auth.EntitlementCanViewResources, auth.EntitlementCanViewMetrics})
return shared.ValueInSlice(entitlement, []auth.Entitlement{auth.EntitlementCanViewResources, auth.EntitlementCanViewMetrics, auth.EntitlementCanViewUnmanagedNetworks})
case entity.TypeIdentity:
// If the entity URL refers to the identity that made the request, then the second path argument of the URL is
// the identifier of the identity. This line allows the caller to view their own identity and no one else's.
Expand Down
5 changes: 5 additions & 0 deletions lxd/auth/entitlements_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions lxd/metadata/configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -7167,6 +7167,10 @@
{
"name": "can_view_warnings",
"description": "Grants permission to view warnings."
},
{
"name": "can_view_unmanaged_networks",
"description": "Grants permission to view unmanaged networks on the LXD host machines."
}
]
},
Expand Down
52 changes: 36 additions & 16 deletions lxd/networks.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,20 +241,36 @@ func networksGet(d *Daemon, r *http.Request) response.Response {

recursion := util.IsRecursionRequest(r)

var networkNames []string
// networks holds the network names of the managed and unmanaged networks. They are in two different slices so that
// we can perform access control checks differently.
var networks [2][]string
const (
managed = iota
unmanaged
)

err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
// Get list of managed networks (that may or may not have network interfaces on the host).
networkNames, err = tx.GetNetworks(ctx, effectiveProjectName)
networks[managed], err = tx.GetNetworks(ctx, effectiveProjectName)

return err
})
if err != nil {
return response.InternalError(err)
}

// Get list of actual network interfaces on the host as well if the effective project is Default.
// Get list of actual network interfaces on the host if the effective project is default and the caller has permission.
var getUnmanagedNetworks bool
if effectiveProjectName == api.ProjectDefaultName {
err := s.Authorizer.CheckPermission(r.Context(), entity.ServerURL(), auth.EntitlementCanViewUnmanagedNetworks)
if err == nil {
getUnmanagedNetworks = true
} else if !auth.IsDeniedError(err) {
return response.SmartError(err)
}
}

if getUnmanagedNetworks {
ifaces, err := net.Interfaces()
if err != nil {
return response.InternalError(err)
Expand All @@ -267,33 +283,37 @@ func networksGet(d *Daemon, r *http.Request) response.Response {
}

// Append to the list of networks if a managed network of same name doesn't exist.
if !shared.ValueInSlice(iface.Name, networkNames) {
networkNames = append(networkNames, iface.Name)
if !shared.ValueInSlice(iface.Name, networks[managed]) {
networks[unmanaged] = append(networks[unmanaged], iface.Name)
}
}
}

// Permission checker works for managed networks only, since they are present in the database.
userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), auth.EntitlementCanView, entity.TypeNetwork)
if err != nil {
return response.InternalError(err)
}

resultString := []string{}
resultMap := []api.Network{}
for _, networkName := range networkNames {
if !userHasPermission(entity.NetworkURL(requestProjectName, networkName)) {
continue
}

if !recursion {
resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", version.APIVersion, networkName))
} else {
net, err := doNetworkGet(s, r, s.ServerClustered, requestProjectName, reqProject.Config, networkName)
if err != nil {
for kind, networkNames := range networks {
for _, networkName := range networkNames {
// Filter out managed networks that the caller doesn't have permission to view.
if kind == managed && !userHasPermission(entity.NetworkURL(requestProjectName, networkName)) {
continue
}

resultMap = append(resultMap, net)
if !recursion {
resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", version.APIVersion, networkName))
} else {
net, err := doNetworkGet(s, r, s.ServerClustered, requestProjectName, reqProject.Config, networkName)
if err != nil {
continue
}

resultMap = append(resultMap, net)
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion test/suites/auth.sh
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ effective_permissions: []"
echo "${list_output}" | grep -Fq 'project,/1.0/projects/default,"can_create_image_aliases,can_create_images,can_create_instances,..."'

list_output="$(lxc auth permission list entity_type=server --format csv --max-entitlements 0)"
echo "${list_output}" | grep -Fq 'server,/1.0,"admin,can_create_groups,can_create_identities,can_create_identity_provider_groups,can_create_projects,can_create_storage_pools,can_delete_groups,can_delete_identities,can_delete_identity_provider_groups,can_delete_projects,can_delete_storage_pools,can_edit,can_edit_groups,can_edit_identities,can_edit_identity_provider_groups,can_edit_projects,can_edit_storage_pools,can_override_cluster_target_restriction,can_view_groups,can_view_identities,can_view_identity_provider_groups,can_view_metrics,can_view_permissions,can_view_privileged_events,can_view_projects,can_view_resources,can_view_warnings,permission_manager,project_manager,storage_pool_manager,viewer"'
echo "${list_output}" | grep -Fq 'server,/1.0,"admin,can_create_groups,can_create_identities,can_create_identity_provider_groups,can_create_projects,can_create_storage_pools,can_delete_groups,can_delete_identities,can_delete_identity_provider_groups,can_delete_projects,can_delete_storage_pools,can_edit,can_edit_groups,can_edit_identities,can_edit_identity_provider_groups,can_edit_projects,can_edit_storage_pools,can_override_cluster_target_restriction,can_view_groups,can_view_identities,can_view_identity_provider_groups,can_view_metrics,can_view_permissions,can_view_privileged_events,can_view_projects,can_view_resources,can_view_unmanaged_networks,can_view_warnings,permission_manager,project_manager,storage_pool_manager,viewer"'

list_output="$(lxc auth permission list entity_type=project --format csv --max-entitlements 0)"
echo "${list_output}" | grep -Fq 'project,/1.0/projects/default,"can_create_image_aliases,can_create_images,can_create_instances,can_create_network_acls,can_create_network_zones,can_create_networks,can_create_profiles,can_create_storage_buckets,can_create_storage_volumes,can_delete,can_delete_image_aliases,can_delete_images,can_delete_instances,can_delete_network_acls,can_delete_network_zones,can_delete_networks,can_delete_profiles,can_delete_storage_buckets,can_delete_storage_volumes,can_edit,can_edit_image_aliases,can_edit_images,can_edit_instances,can_edit_network_acls,can_edit_network_zones,can_edit_networks,can_edit_profiles,can_edit_storage_buckets,can_edit_storage_volumes,can_operate_instances,can_view,can_view_events,can_view_image_aliases,can_view_images,can_view_instances,can_view_metrics,can_view_network_acls,can_view_network_zones,can_view_networks,can_view_operations,can_view_profiles,can_view_storage_buckets,can_view_storage_volumes,image_alias_manager,image_manager,instance_manager,network_acl_manager,network_manager,network_zone_manager,operator,profile_manager,storage_bucket_manager,storage_volume_manager,viewer"'
Expand Down Expand Up @@ -448,6 +448,11 @@ user_is_server_admin() {
lxc_remote storage set "${remote}:test-pool" rsync.compression=true
lxc_remote storage show "${remote}:test-pool" | grep -Fq 'rsync.compression:'
lxc_remote storage delete "${remote}:test-pool"

# Should be able to view all managed and unmanaged networks
host_networks="$(ip a | grep -P '^\d+:' | cut -d' ' -f2 | tr -d ':' | grep -vP '^veth.*' | sort)"
lxd_networks="$(lxc_remote query "${remote}:/1.0/networks?recursion=1" | jq -r '.[].name' | sort)"
[ "${host_networks}" = "${lxd_networks}" ]
}

user_is_server_operator() {
Expand Down
Loading