The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/5540

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===

From f60d98d52b937db5f7119f09b8b32639cc3a61b7 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Thu, 28 Feb 2019 09:33:00 +0100
Subject: [PATCH 1/7] shared/api: Add RBAC API extension

Signed-off-by: Thomas Hipp <[email protected]>
---
 shared/version/api.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/shared/version/api.go b/shared/version/api.go
index f01dc66b22..0b9f77f26f 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -139,6 +139,7 @@ var APIExtensions = []string{
        "snapshot_expiry",
        "container_backup_override_pool",
        "snapshot_expiry_creation",
+       "rbac",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 66266ff7089ef39b2be7439784bac03f8afd5d07 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Thu, 28 Feb 2019 09:56:53 +0100
Subject: [PATCH 2/7] doc: Add RBAC

Signed-off-by: Thomas Hipp <[email protected]>
---
 doc/api-extensions.md | 10 ++++++++++
 doc/server.md         |  6 ++++++
 2 files changed, 16 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 613dbf287f..189d713301 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -693,3 +693,13 @@ This adds the following new endpoint (see [RESTful 
API](rest-api.md) for details
 ## snapshot\_expiry\_creation
 Adds `expires\_at` to container creation, allowing for override of a
 snapshot's expiry at creation time.
+
+## rbac
+Adds support for RBAC (role based access control). This introduces new config 
keys:
+
+  * rbac.api.url
+  * rbac.api.key
+  * rbac.api.expiry
+  * rbac.agent.username
+  * rbac.agent.private_key
+  * rbac.agent.public_key
\ No newline at end of file
diff --git a/doc/server.md b/doc/server.md
index f2b7a18ce0..e5eb55f1dd 100644
--- a/doc/server.md
+++ b/doc/server.md
@@ -35,6 +35,12 @@ images.remote\_cache\_expiry        | integer   | 10        
| -
 maas.api.key                        | string    | -         | maas\_network    
                 | API key to manage MAAS
 maas.api.url                        | string    | -         | maas\_network    
                 | URL of the MAAS server
 maas.machine                        | string    | hostname  | maas\_network    
                 | Name of this LXD host in MAAS
+rbac.agent.public_key               | string    |           | rbac             
                 | The Candid agent public key as provided during RBAC 
registration
+rbac.agent.private_key              | string    |           | rbac             
                 | The Candid agent private key as provided during RBAC 
registration
+rbac.api.key                        | string    |           | rbac             
                 | The RBAC public key
+rbac.api.url                        | string    |           | rbac             
                 | The root URL of the RBAC server
+rbac.api.username                   | string    |           | rbac             
                 | The Candid agent username as provided during RBAC 
registration
+rbac.api.expiry                     | integer   |           | rbac             
                 | TBD
 
 Those keys can be set using the lxc tool with:
 

From bc205b4eb9aac4277c788779e28da3c3ac58e261 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Thu, 28 Feb 2019 09:28:14 +0100
Subject: [PATCH 3/7] lxd: Add RBAC config options

Signed-off-by: Thomas Hipp <[email protected]>
---
 lxd/cluster/config.go | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/lxd/cluster/config.go b/lxd/cluster/config.go
index 3b32f89d00..d2e773ca4c 100644
--- a/lxd/cluster/config.go
+++ b/lxd/cluster/config.go
@@ -132,6 +132,36 @@ func (c *Config) ImagesMinimalReplica() int64 {
        return c.m.GetInt64("cluster.images_minimal_replica")
 }
 
+// RBACAgentPublicKey returns Candid agent public key as provided during RBAC 
registration
+func (c *Config) RBACAgentPublicKey() string {
+       return c.m.GetString("rbac.agent.public_key")
+}
+
+// RBACAgentPrivateKey returns Candid agent private key as provided during 
RBAC registration
+func (c *Config) RBACAgentPrivateKey() string {
+       return c.m.GetString("rbac.agent.private_key")
+}
+
+// RBACAPIURL returns the root URL of the RBAC server
+func (c *Config) RBACAPIURL() string {
+       return c.m.GetString("rbac.api.url")
+}
+
+// RBACAPIUsername returns the Candid agent username as provided during RBAC 
registration
+func (c *Config) RBACAPIUsername() string {
+       return c.m.GetString("rbac.api.username")
+}
+
+// RBACAPIKey returns the RBAC public key
+func (c *Config) RBACAPIKey() string {
+       return c.m.GetString("rbac.api.key")
+}
+
+// RBACAPIExpiry returns the RBAC expiry
+func (c *Config) RBACAPIExpiry() int64 {
+       return c.m.GetInt64("rbac.api.expiry")
+}
+
 // Dump current configuration keys and their values. Keys with values matching
 // their defaults are omitted.
 func (c *Config) Dump() map[string]interface{} {
@@ -242,6 +272,12 @@ var ConfigSchema = config.Schema{
        "images.remote_cache_expiry":     {Type: config.Int64, Default: "10"},
        "maas.api.key":                   {},
        "maas.api.url":                   {},
+       "rbac.agent.private_key":         {},
+       "rbac.agent.public_key":          {},
+       "rbac.api.url":                   {},
+       "rbac.api.username":              {},
+       "rbac.api.key":                   {},
+       "rbac.api.expiry":                {Type: config.Int64, Default: "60"},
 
        // Keys deprecated since the implementation of the storage api.
        "storage.lvm_fstype":           {Setter: deprecatedStorage, Default: 
"ext4"},

From 75f0913e1d754dc8981b283f890bfb10fde15755 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Fri, 1 Mar 2019 11:49:39 +0100
Subject: [PATCH 4/7] lxd/rbac: Add initial code

Signed-off-by: Thomas Hipp <[email protected]>
---
 lxd/rbac/server.go | 243 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 243 insertions(+)
 create mode 100644 lxd/rbac/server.go

diff --git a/lxd/rbac/server.go b/lxd/rbac/server.go
new file mode 100644
index 0000000000..489d7405f7
--- /dev/null
+++ b/lxd/rbac/server.go
@@ -0,0 +1,243 @@
+package rbac
+
+import (
+       "bytes"
+       "encoding/json"
+       "fmt"
+       "net/http"
+       "sync"
+       "time"
+
+       "github.com/juju/persistent-cookiejar"
+       "gopkg.in/macaroon-bakery.v2/bakery"
+       "gopkg.in/macaroon-bakery.v2/httpbakery"
+       "gopkg.in/macaroon-bakery.v2/httpbakery/agent"
+
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/logger"
+)
+
+// UnexpectedSyncIDError is returned when the RBAC server received an 
unexpected sync ID.
+type UnexpectedSyncIDError struct{}
+
+// Error implements error.Error.
+func (e *UnexpectedSyncIDError) Error() string {
+       return "Unexpected sync ID"
+}
+
+type rbacResource struct {
+       Identifier string `json:"identifier"`
+       Name       string `json:"name"`
+}
+
+type rbacResourcePost struct {
+       LastSyncID *string        `json:"last-sync-id"`
+       Updates    []rbacResource `json:"updates"`
+}
+
+type rbacResourcePostResponse struct {
+       SyncID string `json:"sync-id"`
+}
+
+// Server represents an RBAC server.
+type Server struct {
+       url             string
+       publicKey       string
+       username        string
+       agentPrivateKey bakery.PrivateKey
+       agentPublicKey  bakery.PublicKey
+
+       lastSyncID string
+       client     *httpbakery.Client
+
+       resources   map[string]string // Maps name to identifier
+       permissions map[string]map[string][]string
+
+       permissionsLock *sync.Mutex
+}
+
+// NewServer returns a new RBAC server instance.
+func NewServer(URL string, publicKey string, username string, agentPrivateKey 
string,
+       agentPublicKey string) (*Server, error) {
+       r := Server{
+               url:             URL,
+               publicKey:       publicKey,
+               username:        username,
+               lastSyncID:      "",
+               resources:       make(map[string]string),
+               permissions:     make(map[string]map[string][]string),
+               permissionsLock: &sync.Mutex{},
+       }
+
+       r.agentPrivateKey.UnmarshalText([]byte(agentPrivateKey))
+       r.agentPublicKey.UnmarshalText([]byte(agentPublicKey))
+
+       r.client = httpbakery.NewClient()
+
+       authInfo := agent.AuthInfo{
+               Key: &bakery.KeyPair{
+                       Private: r.agentPrivateKey,
+                       Public:  r.agentPublicKey,
+               },
+               Agents: []agent.Agent{
+                       {
+                               URL:      r.url,
+                               Username: r.username,
+                       },
+               },
+       }
+
+       err := agent.SetUpAuth(r.client, &authInfo)
+       if err != nil {
+               return nil, err
+       }
+
+       jar, err := cookiejar.New(&cookiejar.Options{
+               Filename: cookiejar.DefaultCookieFile(),
+       })
+       if err != nil {
+               return nil, err
+       }
+
+       r.client.Client.Jar = jar
+
+       go r.flushCache()
+
+       return &r, nil
+}
+
+// Sync updates resources information for the project resource.
+func (r *Server) Sync(force bool) error {
+       // Get all resources
+       u := fmt.Sprintf("%s/api/service/v1/resources/project", r.url)
+
+       resp, err := r.client.Get(u)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       var resources []rbacResource
+
+       err = json.NewDecoder(resp.Body).Decode(&resources)
+       if err != nil {
+               return err
+       }
+
+       for _, res := range resources {
+               if res.Name != "" {
+                       r.resources[res.Name] = res.Identifier
+               }
+       }
+
+       // Sync
+       var resourcePost rbacResourcePost
+
+       if force || r.lastSyncID == "" {
+               resourcePost.LastSyncID = nil
+       } else {
+               resourcePost.LastSyncID = &r.lastSyncID
+       }
+
+       resourcePost.Updates = resources[1:]
+
+       body, err := json.Marshal(&resourcePost)
+       if err != nil {
+               return err
+       }
+
+       req, err := http.NewRequest("POST", u, bytes.NewReader(body))
+       if err != nil {
+               return err
+       }
+
+       req.Header.Set("Content-Type", "application/json")
+
+       resp, err = r.client.Do(req)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       if resp.StatusCode == 409 {
+               return &UnexpectedSyncIDError{}
+       }
+
+       var postRespose rbacResourcePostResponse
+
+       err = json.NewDecoder(resp.Body).Decode(&postRespose)
+       if err != nil {
+               return err
+       }
+
+       r.lastSyncID = postRespose.SyncID
+
+       logger.Infof("Received RBAC sync ID: %s", r.lastSyncID)
+
+       return nil
+}
+
+// HasPermission returns whether or not the user has the permission to perform 
a certain task.
+func (r *Server) HasPermission(username, project, permission string) bool {
+       r.permissionsLock.Lock()
+       defer r.permissionsLock.Unlock()
+
+       // Check whether the permissions are cached
+       _, cached := r.permissions[username]
+
+       if !cached {
+               r.syncPermissions(username)
+       }
+
+       return shared.StringInSlice(permission, 
r.permissions[username][r.resources[project]])
+}
+
+func (r *Server) flushCache() {
+       for {
+               <-time.After(time.Minute)
+
+               r.permissionsLock.Lock()
+
+               if len(r.permissions) == 0 {
+                       r.permissionsLock.Unlock()
+                       continue
+               }
+
+               logger.Info("Flushing RBAC permissions cache")
+
+               for k, v := range r.permissions {
+                       for k := range v {
+                               delete(v, k)
+                       }
+
+                       delete(r.permissions, k)
+               }
+
+               logger.Info("Flushed RBAC permissions cache")
+
+               r.permissionsLock.Unlock()
+       }
+}
+
+func (r *Server) syncPermissions(username string) error {
+       u := 
fmt.Sprintf("%s/api/service/v1/resources/project/permissions-for-user?u=%s",
+               r.url, username)
+
+       resp, err := r.client.Get(u)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+
+       var permissions map[string][]string
+
+       err = json.NewDecoder(resp.Body).Decode(&permissions)
+       if err != nil {
+               return err
+       }
+
+       // No need to acquire the lock since the caller (HasPermission) already 
has it.
+       r.permissions[username] = permissions
+
+       return nil
+}

From 524ce5b3bcbc3db7320761ead55e3a32aba575b9 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Fri, 1 Mar 2019 10:25:54 +0100
Subject: [PATCH 5/7] lxd: Initialize RBAC

Signed-off-by: Thomas Hipp <[email protected]>
---
 lxd/daemon.go | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 61 insertions(+)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 9059174b97..5cf830dff2 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -31,6 +31,7 @@ import (
        "github.com/lxc/lxd/lxd/endpoints"
        "github.com/lxc/lxd/lxd/maas"
        "github.com/lxc/lxd/lxd/node"
+       "github.com/lxc/lxd/lxd/rbac"
        "github.com/lxc/lxd/lxd/state"
        "github.com/lxc/lxd/lxd/sys"
        "github.com/lxc/lxd/lxd/task"
@@ -49,6 +50,7 @@ type Daemon struct {
        os           *sys.OS
        db           *db.Node
        maas         *maas.Controller
+       rbac         *rbac.Server
        cluster      *db.Cluster
        setupChan    chan struct{} // Closed when basic Daemon setup is 
completed
        readyChan    chan struct{} // Closed when LXD is fully ready
@@ -725,6 +727,11 @@ func (d *Daemon) init() error {
        maasAPIURL := ""
        maasAPIKey := ""
        maasMachine := ""
+       rbacAPIURL := ""
+       rbacAPIKey := ""
+       rbacAgentPrivateKey := ""
+       rbacAgentPublicKey := ""
+       rbacAPIUsername := ""
 
        err = d.db.Transaction(func(tx *db.NodeTx) error {
                config, err := node.ConfigLoad(tx)
@@ -754,6 +761,12 @@ func (d *Daemon) init() error {
                candidExpiry = config.CandidExpiry()
                candidDomains = config.CandidDomains()
                maasAPIURL, maasAPIKey = config.MAASController()
+               rbacAPIURL = config.RBACAPIURL()
+               rbacAPIKey = config.RBACAPIKey()
+               rbacAgentPrivateKey = config.RBACAgentPrivateKey()
+               rbacAgentPublicKey = config.RBACAgentPublicKey()
+               rbacAPIUsername = config.RBACAPIUsername()
+
                return nil
        })
        if err != nil {
@@ -765,6 +778,11 @@ func (d *Daemon) init() error {
                return err
        }
 
+       err = d.setupRBACServer(rbacAPIURL, rbacAPIKey, rbacAPIUsername, 
rbacAgentPrivateKey, rbacAgentPublicKey)
+       if err != nil {
+               return err
+       }
+
        if !d.os.MockMode {
                // Start the scheduler
                go deviceEventListener(d.State())
@@ -1088,6 +1106,49 @@ func (d *Daemon) 
setupExternalAuthentication(authEndpoint string, authPubkey str
        return nil
 }
 
+// Setup RBAC
+func (d *Daemon) setupRBACServer(rbacURL, rbacKey, rbacUsername, 
rbacAgentPrivateKey,
+       rbacAgentPublicKey string) error {
+       d.rbac = nil
+
+       if rbacURL == "" || rbacUsername == "" || rbacAgentPrivateKey == "" || 
rbacAgentPublicKey == "" {
+               return nil
+       }
+
+       // Get a new server struct
+       server, err := rbac.NewServer(rbacURL, rbacKey, rbacUsername, 
rbacAgentPrivateKey, rbacAgentPublicKey)
+       if err != nil {
+               return err
+       }
+
+       // Perform full sync
+       err = server.Sync(true)
+       if err != nil {
+               return err
+       }
+
+       d.rbac = server
+
+       return nil
+}
+
+func (d *Daemon) userHasPermission(r *http.Request, project string, permission 
string) bool {
+       if d.externalAuth == nil || d.rbac == nil {
+               return true
+       }
+
+       valid, userID, err := d.Authenticate(r)
+       if err != nil {
+               return false
+       }
+
+       if valid && userID == "" {
+               return true
+       }
+
+       return d.rbac.HasPermission(userID, project, permission)
+}
+
 // Setup MAAS
 func (d *Daemon) setupMAASController(server string, key string, machine 
string) error {
        var err error

From 8776c6478517f1f34c75e8ae934e1dc4091ad115 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Fri, 1 Mar 2019 16:11:35 +0100
Subject: [PATCH 6/7] lxd: Add RBAC permission checks

Signed-off-by: Thomas Hipp <[email protected]>
---
 lxd/container_backup.go   | 30 ++++++++++++++++++++++++++
 lxd/container_delete.go   |  5 +++++
 lxd/container_exec.go     |  5 +++++
 lxd/container_file.go     |  5 +++++
 lxd/container_get.go      |  6 ++++++
 lxd/container_patch.go    |  4 ++++
 lxd/container_put.go      | 10 +++++++++
 lxd/container_snapshot.go | 26 +++++++++++++++++++++++
 lxd/containers_get.go     |  7 +++++++
 lxd/containers_post.go    |  4 ++++
 lxd/images.go             | 44 +++++++++++++++++++++++++++++++++++++++
 11 files changed, 146 insertions(+)

diff --git a/lxd/container_backup.go b/lxd/container_backup.go
index e50b4d2792..a9f20fa02b 100644
--- a/lxd/container_backup.go
+++ b/lxd/container_backup.go
@@ -19,6 +19,11 @@ import (
 
 func containerBackupsGet(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "view") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        cname := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
@@ -65,6 +70,11 @@ func containerBackupsGet(d *Daemon, r *http.Request) 
Response {
 
 func containerBackupsPost(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
@@ -177,6 +187,11 @@ func containerBackupsPost(d *Daemon, r *http.Request) 
Response {
 
 func containerBackupGet(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "view") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
        backupName := mux.Vars(r)["backupName"]
 
@@ -200,6 +215,11 @@ func containerBackupGet(d *Daemon, r *http.Request) 
Response {
 
 func containerBackupPost(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
        backupName := mux.Vars(r)["backupName"]
 
@@ -254,6 +274,11 @@ func containerBackupPost(d *Daemon, r *http.Request) 
Response {
 
 func containerBackupDelete(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
        backupName := mux.Vars(r)["backupName"]
 
@@ -295,6 +320,11 @@ func containerBackupDelete(d *Daemon, r *http.Request) 
Response {
 
 func containerBackupExportGet(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
        backupName := mux.Vars(r)["backupName"]
 
diff --git a/lxd/container_delete.go b/lxd/container_delete.go
index 6e6d653928..782f7ba9cd 100644
--- a/lxd/container_delete.go
+++ b/lxd/container_delete.go
@@ -10,6 +10,11 @@ import (
 
 func containerDelete(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "manage-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 71c694a06d..d0e9b72740 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -338,6 +338,11 @@ func (s *execWs) Do(op *operation) error {
 
 func containerExecPost(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
 
        post := api.ContainerExecPost{}
diff --git a/lxd/container_file.go b/lxd/container_file.go
index e9358c091a..826fdda6c8 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -15,6 +15,11 @@ import (
 
 func containerFileHandler(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
 
        response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
diff --git a/lxd/container_get.go b/lxd/container_get.go
index 565699364d..937d9e163d 100644
--- a/lxd/container_get.go
+++ b/lxd/container_get.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+       "fmt"
        "net/http"
 
        "github.com/gorilla/mux"
@@ -8,6 +9,11 @@ import (
 
 func containerGet(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "view") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index cb3a28e12c..bb92c319a4 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -19,6 +19,10 @@ import (
 func containerPatch(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
 
+       if !d.userHasPermission(r, project, "manage-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        // Get the container
        name := mux.Vars(r)["name"]
 
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 90a12b7d5e..fd2954cef7 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -60,6 +60,11 @@ func containerPut(d *Daemon, r *http.Request) Response {
        var opType db.OperationType
        if configRaw.Restore == "" {
                // Update container configuration
+
+               if !d.userHasPermission(r, project, "manage-containers") {
+                       return Forbidden(fmt.Errorf("Insufficient permissions"))
+               }
+
                do = func(op *operation) error {
                        args := db.ContainerArgs{
                                Architecture: architecture,
@@ -83,6 +88,11 @@ func containerPut(d *Daemon, r *http.Request) Response {
                opType = db.OperationSnapshotUpdate
        } else {
                // Snapshot Restore
+
+               if !d.userHasPermission(r, project, "operate-containers") {
+                       return Forbidden(fmt.Errorf("Insufficient permissions"))
+               }
+
                do = func(op *operation) error {
                        return containerSnapRestore(d.State(), project, name, 
configRaw.Restore, configRaw.Stateful)
                }
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index 0d3fde4f76..8ec70708f4 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -21,6 +21,11 @@ import (
 
 func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "view") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        cname := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
@@ -76,6 +81,11 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) 
Response {
 
 func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
@@ -198,12 +208,28 @@ func snapshotHandler(d *Daemon, r *http.Request) Response 
{
 
        switch r.Method {
        case "GET":
+               if !d.userHasPermission(r, project, "view") {
+                       return Forbidden(fmt.Errorf("Insufficient permissions"))
+               }
+
                return snapshotGet(sc, snapshotName)
        case "POST":
+               if !d.userHasPermission(r, project, "operate-containers") {
+                       return Forbidden(fmt.Errorf("Insufficient permissions"))
+               }
+
                return snapshotPost(d, r, sc, containerName)
        case "DELETE":
+               if !d.userHasPermission(r, project, "operate-containers") {
+                       return Forbidden(fmt.Errorf("Insufficient permissions"))
+               }
+
                return snapshotDelete(sc, snapshotName)
        case "PUT":
+               if !d.userHasPermission(r, project, "operate-containers") {
+                       return Forbidden(fmt.Errorf("Insufficient permissions"))
+               }
+
                return snapshotPut(d, r, sc, snapshotName)
        default:
                return NotFound(fmt.Errorf("Method '%s' not found", r.Method))
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index d93190be33..3ca888de78 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -19,6 +19,13 @@ import (
 )
 
 func containersGet(d *Daemon, r *http.Request) Response {
+       // Parse the project field
+       project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "view") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        for i := 0; i < 100; i++ {
                result, err := doContainersGet(d, r)
                if err == nil {
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 606f40913a..1103c0f070 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -666,6 +666,10 @@ func containersPost(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
        logger.Debugf("Responding to container create")
 
+       if !d.userHasPermission(r, project, "manage-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        // If we're getting binary content, process separately
        if r.Header.Get("Content-Type") == "application/octet-stream" {
                return createFromBackup(d, project, r.Body, 
r.Header.Get("X-LXD-pool"))
diff --git a/lxd/images.go b/lxd/images.go
index bdd29c9583..4786335c7e 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -657,6 +657,10 @@ func imageCreateInPool(d *Daemon, info *api.Image, 
storagePool string) error {
 func imagesPost(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
 
+       if !d.userHasPermission(r, project, "manage-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        var err error
 
        // create a directory under which we keep everything while building
@@ -881,6 +885,11 @@ func doImagesGet(d *Daemon, recursion bool, project 
string, public bool) (interf
 
 func imagesGet(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "view") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        public := d.checkTrustedClient(r) != nil
 
        result, err := doImagesGet(d, util.IsRecursionRequest(r), project, 
public)
@@ -1308,6 +1317,11 @@ func doDeleteImageFromPool(state *state.State, 
fingerprint string, storagePool s
 
 func imageDelete(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "manage-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        fingerprint := mux.Vars(r)["fingerprint"]
 
        deleteFromAllPools := func() error {
@@ -1483,6 +1497,11 @@ func imageValidSecret(fingerprint string, secret string) 
bool {
 
 func imageGet(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "view") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        fingerprint := mux.Vars(r)["fingerprint"]
        public := d.checkTrustedClient(r) != nil
        secret := r.FormValue("secret")
@@ -1503,6 +1522,11 @@ func imageGet(d *Daemon, r *http.Request) Response {
 func imagePut(d *Daemon, r *http.Request) Response {
        // Get current value
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "manage-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        fingerprint := mux.Vars(r)["fingerprint"]
        id, info, err := d.cluster.ImageGet(project, fingerprint, false, false)
        if err != nil {
@@ -1532,6 +1556,11 @@ func imagePut(d *Daemon, r *http.Request) Response {
 func imagePatch(d *Daemon, r *http.Request) Response {
        // Get current value
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "manage-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        fingerprint := mux.Vars(r)["fingerprint"]
        id, info, err := d.cluster.ImageGet(project, fingerprint, false, false)
        if err != nil {
@@ -1809,6 +1838,11 @@ func aliasPost(d *Daemon, r *http.Request) Response {
 
 func imageExport(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        fingerprint := mux.Vars(r)["fingerprint"]
 
        public := d.checkTrustedClient(r) != nil
@@ -1893,6 +1927,11 @@ func imageExport(d *Daemon, r *http.Request) Response {
 
 func imageSecret(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        fingerprint := mux.Vars(r)["fingerprint"]
        _, imgInfo, err := d.cluster.ImageGet(project, fingerprint, false, 
false)
        if err != nil {
@@ -1977,6 +2016,11 @@ func imageImportFromNode(imagesDir string, client 
lxd.ContainerServer, fingerpri
 
 func imageRefresh(d *Daemon, r *http.Request) Response {
        project := projectParam(r)
+
+       if !d.userHasPermission(r, project, "operate-containers") {
+               return Forbidden(fmt.Errorf("Insufficient permissions"))
+       }
+
        fingerprint := mux.Vars(r)["fingerprint"]
        imageId, imageInfo, err := d.cluster.ImageGet(project, fingerprint, 
false, false)
        if err != nil {

From c93c81e8b3aca5416b373aab3621d6a04b3f5543 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <[email protected]>
Date: Fri, 1 Mar 2019 23:21:30 +0100
Subject: [PATCH 7/7] lxd: Get username from context for RBAC

Signed-off-by: Thomas Hipp <[email protected]>
---
 lxd/daemon.go | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 5cf830dff2..e6f28963aa 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -1133,20 +1133,11 @@ func (d *Daemon) setupRBACServer(rbacURL, rbacKey, 
rbacUsername, rbacAgentPrivat
 }
 
 func (d *Daemon) userHasPermission(r *http.Request, project string, permission 
string) bool {
-       if d.externalAuth == nil || d.rbac == nil {
+       if d.externalAuth == nil || d.rbac == nil || r.RemoteAddr == "@" {
                return true
        }
 
-       valid, userID, err := d.Authenticate(r)
-       if err != nil {
-               return false
-       }
-
-       if valid && userID == "" {
-               return true
-       }
-
-       return d.rbac.HasPermission(userID, project, permission)
+       return d.rbac.HasPermission(r.Context().Value("username").(string), 
project, permission)
 }
 
 // Setup MAAS
_______________________________________________
lxc-devel mailing list
[email protected]
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to