2019-11-12 23:41:42 +00:00
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
2021-03-24 18:27:32 +00:00
|
|
|
"bytes"
|
2019-11-12 23:41:42 +00:00
|
|
|
"encoding/base64"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2021-07-14 09:15:21 +00:00
|
|
|
"fmt"
|
2021-03-24 18:27:32 +00:00
|
|
|
"io/ioutil"
|
2019-11-12 23:41:42 +00:00
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"path"
|
|
|
|
"regexp"
|
2021-07-14 09:15:21 +00:00
|
|
|
"strconv"
|
2019-11-12 23:41:42 +00:00
|
|
|
"strings"
|
|
|
|
|
2021-02-09 08:09:06 +00:00
|
|
|
portainer "github.com/portainer/portainer/api"
|
chore(store) EE-1981: Refactor/store/error checking, and other refactoring (#6173)
* use the Store interface IsErrObjectNotFound() to avoid revealing internal errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* what happens when you extract the datastore interfaces into their own package
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* Start renaming Storage methods
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the boltdb specific code from the Portainer storage code (example, the others need the same)
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extract bolt.Tx from datastore code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* minimise imports by putting moving the struct definition into the file that needs the Service imports
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extraction of boltdb.Tx
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the use of bucket.SetSequence
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* almost done - just endpoint.Synchonise :/
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* so, endpoint.Synchonize looks hard, but i can't find where we use it, so 'delete first refactoring'
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix test compile errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* test compile fixes after rebase
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix a mis-remembering I had wrt deserialisation - last time i used AnyData - jsoniter's bindTo looks interesting for the same reason
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* set us up to make the connection an interface
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make the db connection a datastore interface, and separate out our datastore services from the bolt ones
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* rename methods to something less oltdb internals specific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* these errors are not boltdb secific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* start using the db-backend factory method too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* export boltdb raw in case we can't export from the service layer
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add a raw export from boltdb to yaml for broken db's, and an export services to yaml in backup
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add the version info by hand for now
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* actually, the export from services can be fully typed - its the import that needs to do more work
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* redo raw export, and make import capable of using it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add DockerHub
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* migration from anything older than v1.21.0 has been broken for quite a while, deleting the un-tested code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix go test ./... again
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* my goland wasn't setup to gofmt
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the two extremely dubious migration tests down into store, so they can use the test store code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* the migrator is now free of boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* reverse goland overzealous replcement of internal with boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more undo over-zealous goland internal->boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* yay, now bolt is only mentioned inside the api/database/ dir
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* and this might be the last of the boltdb references?
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add todo
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the store code into a separate module too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* don't need the fileService in boltdb anymore
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use IsErrObjectNotFound()
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use a string to select what database backend we use
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make isNew store an ephemeral bool that doesn't stay true after we've initialised it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the import.json wip to a separate file so its more obvious - we'll be using it for testing, emergency fixups, and in the next part of the store work, when we improve migrations and data model lifecycles
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* undo vscode formatting html
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix app templates symbol (#6221)
* feat(webhook) EE-2125 send registry auth haeder when update swarms service via webhook (#6220)
* feat(webhook) EE-2125 add some helpers to registry utils
* feat(webhook) EE-2125 persist registryID when creating a webhook
* feat(webhook) EE-2125 send registry auth header when executing a webhook
* feat(webhook) EE-2125 send registryID to backend when creating a service with webhook
* feat(webhook) EE-2125 use the initial registry ID to create webhook on editing service screen
* feat(webhook) EE-2125 update webhook when update registry
* feat(webhook) EE-2125 add endpoint of update webhook
* feat(webhook) EE-2125 code cleanup
* feat(webhook) EE-2125 fix a typo
* feat(webhook) EE-2125 fix circle import issue with unit test
Co-authored-by: Simon Meng <simon.meng@portainer.io>
* fix(kubeconfig): show kubeconfig download button for non admin users [EE-2123] (#6204)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
* fix data-cy for k8s cluster menu (#6226)
LGTM
* feat(stack): make stack created from app template editable EE-1941 (#6104)
feat(stack): make stack from app template editable
* fix(container):disable Duplicate/Edit button when the container is portainer (#6223)
* fix/ee-1909/show-pull-image-error (#6195)
Co-authored-by: sunportainer <ericsun@SG1.local>
* feat(cy): add data-cy to helm install button (#6241)
* feat(cy): add data-cy to add registry button (#6242)
* refactor(app): convert root folder files to es6 (#4159)
* refactor(app): duplicate constants as es6 exports (#4158)
* fix(docker): provide workaround to save network name variable (#6080)
* fix/EE-1862/unable-to-stop-or-remove-stack workaround for var without default value in yaml file
* fix/EE-1862/unable-to-stop-or-remove-stack check yaml file
* fixed func and var names
* wrapper error and used bool for stringset
* UT case for createNetworkEnvFile
* UT case for %s=%s
* powerful StringSet
* wrapper error for extract network name
* wrapper all the return err
* store more env
* put to env file
* make default value None
* feat: gzip static resources (#6258)
* fix(ssl)//handle --sslcert and --sslkey ee-2106 (#6203)
* fix/ee-2106/handle-sslcert-sslkey
Co-authored-by: sunportainer <ericsun@SG1.local>
* fix(server):support disable https only ee-2068 (#6232)
* fix/ee-2068/disable-forcely-https
* feat(store): implement store tests EE-2112 (#6224)
* add store tests
* add some more tests
* Update missing helm user repo methods
* remove redundant comments
* add webhook export
* update webhooks
* use the Store interface IsErrObjectNotFound() to avoid revealing internal errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* what happens when you extract the datastore interfaces into their own package
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* Start renaming Storage methods
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the boltdb specific code from the Portainer storage code (example, the others need the same)
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extract bolt.Tx from datastore code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* minimise imports by putting moving the struct definition into the file that needs the Service imports
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extraction of boltdb.Tx
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the use of bucket.SetSequence
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* almost done - just endpoint.Synchonise :/
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* so, endpoint.Synchonize looks hard, but i can't find where we use it, so 'delete first refactoring'
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix test compile errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* test compile fixes after rebase
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix a mis-remembering I had wrt deserialisation - last time i used AnyData - jsoniter's bindTo looks interesting for the same reason
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* set us up to make the connection an interface
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make the db connection a datastore interface, and separate out our datastore services from the bolt ones
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* rename methods to something less oltdb internals specific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* these errors are not boltdb secific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* start using the db-backend factory method too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* export boltdb raw in case we can't export from the service layer
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add a raw export from boltdb to yaml for broken db's, and an export services to yaml in backup
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add the version info by hand for now
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* actually, the export from services can be fully typed - its the import that needs to do more work
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* redo raw export, and make import capable of using it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add DockerHub
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* migration from anything older than v1.21.0 has been broken for quite a while, deleting the un-tested code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix go test ./... again
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* my goland wasn't setup to gofmt
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the two extremely dubious migration tests down into store, so they can use the test store code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* the migrator is now free of boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* reverse goland overzealous replcement of internal with boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more undo over-zealous goland internal->boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* yay, now bolt is only mentioned inside the api/database/ dir
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* and this might be the last of the boltdb references?
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add todo
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the store code into a separate module too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* don't need the fileService in boltdb anymore
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use IsErrObjectNotFound()
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use a string to select what database backend we use
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make isNew store an ephemeral bool that doesn't stay true after we've initialised it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the import.json wip to a separate file so its more obvious - we'll be using it for testing, emergency fixups, and in the next part of the store work, when we improve migrations and data model lifecycles
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* undo vscode formatting html
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* Update missing helm user repo methods
* feat(store): implement store tests EE-2112 (#6224)
* add store tests
* add some more tests
* remove redundant comments
* add webhook export
* update webhooks
* fix build issues after rebasing
* move migratorparams
* remove unneeded integer type conversions
* disable the db import/export for now
Co-authored-by: Richard Wei <54336863+WaysonWei@users.noreply.github.com>
Co-authored-by: cong meng <mcpacino@gmail.com>
Co-authored-by: Simon Meng <simon.meng@portainer.io>
Co-authored-by: Marcelo Rydel <marcelorydel26@gmail.com>
Co-authored-by: Hao Zhang <hao.zhang@portainer.io>
Co-authored-by: sunportainer <93502624+sunportainer@users.noreply.github.com>
Co-authored-by: sunportainer <ericsun@SG1.local>
Co-authored-by: wheresolivia <78844659+wheresolivia@users.noreply.github.com>
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
Co-authored-by: Chao Geng <93526589+chaogeng77977@users.noreply.github.com>
Co-authored-by: Dmitry Salakhov <to@dimasalakhov.com>
Co-authored-by: Matt Hook <hookenz@gmail.com>
2021-12-15 02:26:09 +00:00
|
|
|
"github.com/portainer/portainer/api/dataservices"
|
2022-01-23 07:17:31 +00:00
|
|
|
dataerrors "github.com/portainer/portainer/api/dataservices/errors"
|
2020-06-16 07:58:16 +00:00
|
|
|
"github.com/portainer/portainer/api/docker"
|
2021-07-14 09:15:21 +00:00
|
|
|
"github.com/portainer/portainer/api/http/proxy/factory/utils"
|
2019-11-12 23:41:42 +00:00
|
|
|
"github.com/portainer/portainer/api/http/security"
|
2020-06-16 07:58:16 +00:00
|
|
|
"github.com/portainer/portainer/api/internal/authorization"
|
2019-11-12 23:41:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var apiVersionRe = regexp.MustCompile(`(/v[0-9]\.[0-9]*)?`)
|
|
|
|
|
|
|
|
type (
|
|
|
|
// Transport is a custom transport for Docker API reverse proxy. It allows
|
|
|
|
// interception of requests and rewriting of responses.
|
|
|
|
Transport struct {
|
2020-05-20 05:23:15 +00:00
|
|
|
HTTPTransport *http.Transport
|
|
|
|
endpoint *portainer.Endpoint
|
chore(store) EE-1981: Refactor/store/error checking, and other refactoring (#6173)
* use the Store interface IsErrObjectNotFound() to avoid revealing internal errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* what happens when you extract the datastore interfaces into their own package
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* Start renaming Storage methods
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the boltdb specific code from the Portainer storage code (example, the others need the same)
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extract bolt.Tx from datastore code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* minimise imports by putting moving the struct definition into the file that needs the Service imports
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extraction of boltdb.Tx
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the use of bucket.SetSequence
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* almost done - just endpoint.Synchonise :/
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* so, endpoint.Synchonize looks hard, but i can't find where we use it, so 'delete first refactoring'
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix test compile errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* test compile fixes after rebase
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix a mis-remembering I had wrt deserialisation - last time i used AnyData - jsoniter's bindTo looks interesting for the same reason
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* set us up to make the connection an interface
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make the db connection a datastore interface, and separate out our datastore services from the bolt ones
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* rename methods to something less oltdb internals specific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* these errors are not boltdb secific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* start using the db-backend factory method too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* export boltdb raw in case we can't export from the service layer
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add a raw export from boltdb to yaml for broken db's, and an export services to yaml in backup
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add the version info by hand for now
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* actually, the export from services can be fully typed - its the import that needs to do more work
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* redo raw export, and make import capable of using it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add DockerHub
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* migration from anything older than v1.21.0 has been broken for quite a while, deleting the un-tested code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix go test ./... again
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* my goland wasn't setup to gofmt
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the two extremely dubious migration tests down into store, so they can use the test store code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* the migrator is now free of boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* reverse goland overzealous replcement of internal with boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more undo over-zealous goland internal->boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* yay, now bolt is only mentioned inside the api/database/ dir
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* and this might be the last of the boltdb references?
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add todo
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the store code into a separate module too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* don't need the fileService in boltdb anymore
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use IsErrObjectNotFound()
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use a string to select what database backend we use
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make isNew store an ephemeral bool that doesn't stay true after we've initialised it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the import.json wip to a separate file so its more obvious - we'll be using it for testing, emergency fixups, and in the next part of the store work, when we improve migrations and data model lifecycles
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* undo vscode formatting html
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix app templates symbol (#6221)
* feat(webhook) EE-2125 send registry auth haeder when update swarms service via webhook (#6220)
* feat(webhook) EE-2125 add some helpers to registry utils
* feat(webhook) EE-2125 persist registryID when creating a webhook
* feat(webhook) EE-2125 send registry auth header when executing a webhook
* feat(webhook) EE-2125 send registryID to backend when creating a service with webhook
* feat(webhook) EE-2125 use the initial registry ID to create webhook on editing service screen
* feat(webhook) EE-2125 update webhook when update registry
* feat(webhook) EE-2125 add endpoint of update webhook
* feat(webhook) EE-2125 code cleanup
* feat(webhook) EE-2125 fix a typo
* feat(webhook) EE-2125 fix circle import issue with unit test
Co-authored-by: Simon Meng <simon.meng@portainer.io>
* fix(kubeconfig): show kubeconfig download button for non admin users [EE-2123] (#6204)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
* fix data-cy for k8s cluster menu (#6226)
LGTM
* feat(stack): make stack created from app template editable EE-1941 (#6104)
feat(stack): make stack from app template editable
* fix(container):disable Duplicate/Edit button when the container is portainer (#6223)
* fix/ee-1909/show-pull-image-error (#6195)
Co-authored-by: sunportainer <ericsun@SG1.local>
* feat(cy): add data-cy to helm install button (#6241)
* feat(cy): add data-cy to add registry button (#6242)
* refactor(app): convert root folder files to es6 (#4159)
* refactor(app): duplicate constants as es6 exports (#4158)
* fix(docker): provide workaround to save network name variable (#6080)
* fix/EE-1862/unable-to-stop-or-remove-stack workaround for var without default value in yaml file
* fix/EE-1862/unable-to-stop-or-remove-stack check yaml file
* fixed func and var names
* wrapper error and used bool for stringset
* UT case for createNetworkEnvFile
* UT case for %s=%s
* powerful StringSet
* wrapper error for extract network name
* wrapper all the return err
* store more env
* put to env file
* make default value None
* feat: gzip static resources (#6258)
* fix(ssl)//handle --sslcert and --sslkey ee-2106 (#6203)
* fix/ee-2106/handle-sslcert-sslkey
Co-authored-by: sunportainer <ericsun@SG1.local>
* fix(server):support disable https only ee-2068 (#6232)
* fix/ee-2068/disable-forcely-https
* feat(store): implement store tests EE-2112 (#6224)
* add store tests
* add some more tests
* Update missing helm user repo methods
* remove redundant comments
* add webhook export
* update webhooks
* use the Store interface IsErrObjectNotFound() to avoid revealing internal errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* what happens when you extract the datastore interfaces into their own package
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* Start renaming Storage methods
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the boltdb specific code from the Portainer storage code (example, the others need the same)
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extract bolt.Tx from datastore code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* minimise imports by putting moving the struct definition into the file that needs the Service imports
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extraction of boltdb.Tx
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the use of bucket.SetSequence
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* almost done - just endpoint.Synchonise :/
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* so, endpoint.Synchonize looks hard, but i can't find where we use it, so 'delete first refactoring'
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix test compile errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* test compile fixes after rebase
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix a mis-remembering I had wrt deserialisation - last time i used AnyData - jsoniter's bindTo looks interesting for the same reason
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* set us up to make the connection an interface
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make the db connection a datastore interface, and separate out our datastore services from the bolt ones
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* rename methods to something less oltdb internals specific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* these errors are not boltdb secific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* start using the db-backend factory method too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* export boltdb raw in case we can't export from the service layer
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add a raw export from boltdb to yaml for broken db's, and an export services to yaml in backup
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add the version info by hand for now
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* actually, the export from services can be fully typed - its the import that needs to do more work
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* redo raw export, and make import capable of using it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add DockerHub
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* migration from anything older than v1.21.0 has been broken for quite a while, deleting the un-tested code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix go test ./... again
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* my goland wasn't setup to gofmt
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the two extremely dubious migration tests down into store, so they can use the test store code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* the migrator is now free of boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* reverse goland overzealous replcement of internal with boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more undo over-zealous goland internal->boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* yay, now bolt is only mentioned inside the api/database/ dir
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* and this might be the last of the boltdb references?
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add todo
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the store code into a separate module too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* don't need the fileService in boltdb anymore
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use IsErrObjectNotFound()
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use a string to select what database backend we use
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make isNew store an ephemeral bool that doesn't stay true after we've initialised it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the import.json wip to a separate file so its more obvious - we'll be using it for testing, emergency fixups, and in the next part of the store work, when we improve migrations and data model lifecycles
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* undo vscode formatting html
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* Update missing helm user repo methods
* feat(store): implement store tests EE-2112 (#6224)
* add store tests
* add some more tests
* remove redundant comments
* add webhook export
* update webhooks
* fix build issues after rebasing
* move migratorparams
* remove unneeded integer type conversions
* disable the db import/export for now
Co-authored-by: Richard Wei <54336863+WaysonWei@users.noreply.github.com>
Co-authored-by: cong meng <mcpacino@gmail.com>
Co-authored-by: Simon Meng <simon.meng@portainer.io>
Co-authored-by: Marcelo Rydel <marcelorydel26@gmail.com>
Co-authored-by: Hao Zhang <hao.zhang@portainer.io>
Co-authored-by: sunportainer <93502624+sunportainer@users.noreply.github.com>
Co-authored-by: sunportainer <ericsun@SG1.local>
Co-authored-by: wheresolivia <78844659+wheresolivia@users.noreply.github.com>
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
Co-authored-by: Chao Geng <93526589+chaogeng77977@users.noreply.github.com>
Co-authored-by: Dmitry Salakhov <to@dimasalakhov.com>
Co-authored-by: Matt Hook <hookenz@gmail.com>
2021-12-15 02:26:09 +00:00
|
|
|
dataStore dataservices.DataStore
|
2020-05-20 05:23:15 +00:00
|
|
|
signatureService portainer.DigitalSignatureService
|
|
|
|
reverseTunnelService portainer.ReverseTunnelService
|
|
|
|
dockerClientFactory *docker.ClientFactory
|
2022-05-15 21:35:11 +00:00
|
|
|
gitService portainer.GitService
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TransportParameters is used to create a new Transport
|
|
|
|
TransportParameters struct {
|
2020-05-20 05:23:15 +00:00
|
|
|
Endpoint *portainer.Endpoint
|
chore(store) EE-1981: Refactor/store/error checking, and other refactoring (#6173)
* use the Store interface IsErrObjectNotFound() to avoid revealing internal errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* what happens when you extract the datastore interfaces into their own package
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* Start renaming Storage methods
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the boltdb specific code from the Portainer storage code (example, the others need the same)
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extract bolt.Tx from datastore code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* minimise imports by putting moving the struct definition into the file that needs the Service imports
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extraction of boltdb.Tx
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the use of bucket.SetSequence
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* almost done - just endpoint.Synchonise :/
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* so, endpoint.Synchonize looks hard, but i can't find where we use it, so 'delete first refactoring'
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix test compile errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* test compile fixes after rebase
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix a mis-remembering I had wrt deserialisation - last time i used AnyData - jsoniter's bindTo looks interesting for the same reason
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* set us up to make the connection an interface
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make the db connection a datastore interface, and separate out our datastore services from the bolt ones
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* rename methods to something less oltdb internals specific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* these errors are not boltdb secific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* start using the db-backend factory method too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* export boltdb raw in case we can't export from the service layer
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add a raw export from boltdb to yaml for broken db's, and an export services to yaml in backup
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add the version info by hand for now
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* actually, the export from services can be fully typed - its the import that needs to do more work
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* redo raw export, and make import capable of using it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add DockerHub
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* migration from anything older than v1.21.0 has been broken for quite a while, deleting the un-tested code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix go test ./... again
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* my goland wasn't setup to gofmt
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the two extremely dubious migration tests down into store, so they can use the test store code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* the migrator is now free of boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* reverse goland overzealous replcement of internal with boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more undo over-zealous goland internal->boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* yay, now bolt is only mentioned inside the api/database/ dir
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* and this might be the last of the boltdb references?
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add todo
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the store code into a separate module too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* don't need the fileService in boltdb anymore
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use IsErrObjectNotFound()
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use a string to select what database backend we use
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make isNew store an ephemeral bool that doesn't stay true after we've initialised it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the import.json wip to a separate file so its more obvious - we'll be using it for testing, emergency fixups, and in the next part of the store work, when we improve migrations and data model lifecycles
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* undo vscode formatting html
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix app templates symbol (#6221)
* feat(webhook) EE-2125 send registry auth haeder when update swarms service via webhook (#6220)
* feat(webhook) EE-2125 add some helpers to registry utils
* feat(webhook) EE-2125 persist registryID when creating a webhook
* feat(webhook) EE-2125 send registry auth header when executing a webhook
* feat(webhook) EE-2125 send registryID to backend when creating a service with webhook
* feat(webhook) EE-2125 use the initial registry ID to create webhook on editing service screen
* feat(webhook) EE-2125 update webhook when update registry
* feat(webhook) EE-2125 add endpoint of update webhook
* feat(webhook) EE-2125 code cleanup
* feat(webhook) EE-2125 fix a typo
* feat(webhook) EE-2125 fix circle import issue with unit test
Co-authored-by: Simon Meng <simon.meng@portainer.io>
* fix(kubeconfig): show kubeconfig download button for non admin users [EE-2123] (#6204)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
* fix data-cy for k8s cluster menu (#6226)
LGTM
* feat(stack): make stack created from app template editable EE-1941 (#6104)
feat(stack): make stack from app template editable
* fix(container):disable Duplicate/Edit button when the container is portainer (#6223)
* fix/ee-1909/show-pull-image-error (#6195)
Co-authored-by: sunportainer <ericsun@SG1.local>
* feat(cy): add data-cy to helm install button (#6241)
* feat(cy): add data-cy to add registry button (#6242)
* refactor(app): convert root folder files to es6 (#4159)
* refactor(app): duplicate constants as es6 exports (#4158)
* fix(docker): provide workaround to save network name variable (#6080)
* fix/EE-1862/unable-to-stop-or-remove-stack workaround for var without default value in yaml file
* fix/EE-1862/unable-to-stop-or-remove-stack check yaml file
* fixed func and var names
* wrapper error and used bool for stringset
* UT case for createNetworkEnvFile
* UT case for %s=%s
* powerful StringSet
* wrapper error for extract network name
* wrapper all the return err
* store more env
* put to env file
* make default value None
* feat: gzip static resources (#6258)
* fix(ssl)//handle --sslcert and --sslkey ee-2106 (#6203)
* fix/ee-2106/handle-sslcert-sslkey
Co-authored-by: sunportainer <ericsun@SG1.local>
* fix(server):support disable https only ee-2068 (#6232)
* fix/ee-2068/disable-forcely-https
* feat(store): implement store tests EE-2112 (#6224)
* add store tests
* add some more tests
* Update missing helm user repo methods
* remove redundant comments
* add webhook export
* update webhooks
* use the Store interface IsErrObjectNotFound() to avoid revealing internal errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* what happens when you extract the datastore interfaces into their own package
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* Start renaming Storage methods
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the boltdb specific code from the Portainer storage code (example, the others need the same)
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extract bolt.Tx from datastore code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* minimise imports by putting moving the struct definition into the file that needs the Service imports
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more extraction of boltdb.Tx
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the use of bucket.SetSequence
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* almost done - just endpoint.Synchonise :/
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* so, endpoint.Synchonize looks hard, but i can't find where we use it, so 'delete first refactoring'
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix test compile errors
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* test compile fixes after rebase
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix a mis-remembering I had wrt deserialisation - last time i used AnyData - jsoniter's bindTo looks interesting for the same reason
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* set us up to make the connection an interface
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make the db connection a datastore interface, and separate out our datastore services from the bolt ones
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* rename methods to something less oltdb internals specific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* these errors are not boltdb secific
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* start using the db-backend factory method too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* export boltdb raw in case we can't export from the service layer
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add a raw export from boltdb to yaml for broken db's, and an export services to yaml in backup
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add the version info by hand for now
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* actually, the export from services can be fully typed - its the import that needs to do more work
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* redo raw export, and make import capable of using it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add DockerHub
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* migration from anything older than v1.21.0 has been broken for quite a while, deleting the un-tested code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* fix go test ./... again
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* my goland wasn't setup to gofmt
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the two extremely dubious migration tests down into store, so they can use the test store code
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* the migrator is now free of boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* reverse goland overzealous replcement of internal with boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* more undo over-zealous goland internal->boltdb
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* yay, now bolt is only mentioned inside the api/database/ dir
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* and this might be the last of the boltdb references?
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* add todo
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* extract the store code into a separate module too
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* don't need the fileService in boltdb anymore
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use IsErrObjectNotFound()
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* use a string to select what database backend we use
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* make isNew store an ephemeral bool that doesn't stay true after we've initialised it
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* move the import.json wip to a separate file so its more obvious - we'll be using it for testing, emergency fixups, and in the next part of the store work, when we improve migrations and data model lifecycles
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* undo vscode formatting html
Signed-off-by: Sven Dowideit <sven.dowideit@portainer.io>
* Update missing helm user repo methods
* feat(store): implement store tests EE-2112 (#6224)
* add store tests
* add some more tests
* remove redundant comments
* add webhook export
* update webhooks
* fix build issues after rebasing
* move migratorparams
* remove unneeded integer type conversions
* disable the db import/export for now
Co-authored-by: Richard Wei <54336863+WaysonWei@users.noreply.github.com>
Co-authored-by: cong meng <mcpacino@gmail.com>
Co-authored-by: Simon Meng <simon.meng@portainer.io>
Co-authored-by: Marcelo Rydel <marcelorydel26@gmail.com>
Co-authored-by: Hao Zhang <hao.zhang@portainer.io>
Co-authored-by: sunportainer <93502624+sunportainer@users.noreply.github.com>
Co-authored-by: sunportainer <ericsun@SG1.local>
Co-authored-by: wheresolivia <78844659+wheresolivia@users.noreply.github.com>
Co-authored-by: Chaim Lev-Ari <chiptus@users.noreply.github.com>
Co-authored-by: Chao Geng <93526589+chaogeng77977@users.noreply.github.com>
Co-authored-by: Dmitry Salakhov <to@dimasalakhov.com>
Co-authored-by: Matt Hook <hookenz@gmail.com>
2021-12-15 02:26:09 +00:00
|
|
|
DataStore dataservices.DataStore
|
2020-05-20 05:23:15 +00:00
|
|
|
SignatureService portainer.DigitalSignatureService
|
|
|
|
ReverseTunnelService portainer.ReverseTunnelService
|
|
|
|
DockerClientFactory *docker.ClientFactory
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
restrictedDockerOperationContext struct {
|
2020-08-24 23:04:51 +00:00
|
|
|
isAdmin bool
|
|
|
|
userID portainer.UserID
|
|
|
|
userTeamIDs []portainer.TeamID
|
|
|
|
resourceControls []portainer.ResourceControl
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
operationExecutor struct {
|
|
|
|
operationContext *restrictedDockerOperationContext
|
|
|
|
labelBlackList []portainer.Pair
|
|
|
|
}
|
|
|
|
restrictedOperationRequest func(*http.Response, *operationExecutor) error
|
|
|
|
operationRequest func(*http.Request) error
|
|
|
|
)
|
|
|
|
|
|
|
|
// NewTransport returns a pointer to a new Transport instance.
|
2022-05-15 21:35:11 +00:00
|
|
|
func NewTransport(parameters *TransportParameters, httpTransport *http.Transport, gitService portainer.GitService) (*Transport, error) {
|
2019-11-12 23:41:42 +00:00
|
|
|
transport := &Transport{
|
2020-05-20 05:23:15 +00:00
|
|
|
endpoint: parameters.Endpoint,
|
|
|
|
dataStore: parameters.DataStore,
|
|
|
|
signatureService: parameters.SignatureService,
|
|
|
|
reverseTunnelService: parameters.ReverseTunnelService,
|
|
|
|
dockerClientFactory: parameters.DockerClientFactory,
|
|
|
|
HTTPTransport: httpTransport,
|
2022-05-15 21:35:11 +00:00
|
|
|
gitService: gitService,
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return transport, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RoundTrip is the implementation of the the http.RoundTripper interface
|
|
|
|
func (transport *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
|
|
|
|
return transport.ProxyDockerRequest(request)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ProxyDockerRequest intercepts a Docker API request and apply logic based
|
|
|
|
// on the requested operation.
|
|
|
|
func (transport *Transport) ProxyDockerRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
requestPath := apiVersionRe.ReplaceAllString(request.URL.Path, "")
|
|
|
|
request.URL.Path = requestPath
|
|
|
|
|
2021-08-09 05:22:41 +00:00
|
|
|
if transport.endpoint.Type == portainer.AgentOnDockerEnvironment || transport.endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
|
2019-11-12 23:41:42 +00:00
|
|
|
signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
request.Header.Set(portainer.PortainerAgentPublicKeyHeader, transport.signatureService.EncodedPublicKey())
|
|
|
|
request.Header.Set(portainer.PortainerAgentSignatureHeader, signature)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case strings.HasPrefix(requestPath, "/configs"):
|
|
|
|
return transport.proxyConfigRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/containers"):
|
|
|
|
return transport.proxyContainerRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/services"):
|
|
|
|
return transport.proxyServiceRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/volumes"):
|
|
|
|
return transport.proxyVolumeRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/networks"):
|
|
|
|
return transport.proxyNetworkRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/secrets"):
|
|
|
|
return transport.proxySecretRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/swarm"):
|
|
|
|
return transport.proxySwarmRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/nodes"):
|
|
|
|
return transport.proxyNodeRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/tasks"):
|
|
|
|
return transport.proxyTaskRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/build"):
|
|
|
|
return transport.proxyBuildRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/images"):
|
|
|
|
return transport.proxyImageRequest(request)
|
|
|
|
case strings.HasPrefix(requestPath, "/v2"):
|
|
|
|
return transport.proxyAgentRequest(request)
|
|
|
|
default:
|
|
|
|
return transport.executeDockerRequest(request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) executeDockerRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
response, err := transport.HTTPTransport.RoundTrip(request)
|
|
|
|
|
2020-07-05 23:21:03 +00:00
|
|
|
if transport.endpoint.Type != portainer.EdgeAgentOnDockerEnvironment {
|
2019-11-12 23:41:42 +00:00
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
transport.reverseTunnelService.SetTunnelStatusToActive(transport.endpoint.ID)
|
|
|
|
} else {
|
|
|
|
transport.reverseTunnelService.SetTunnelStatusToIdle(transport.endpoint.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxyAgentRequest(r *http.Request) (*http.Response, error) {
|
|
|
|
requestPath := strings.TrimPrefix(r.URL.Path, "/v2")
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case strings.HasPrefix(requestPath, "/browse"):
|
2020-05-12 04:08:01 +00:00
|
|
|
// host file browser request
|
2019-11-12 23:41:42 +00:00
|
|
|
volumeIDParameter, found := r.URL.Query()["volumeID"]
|
|
|
|
if !found || len(volumeIDParameter) < 1 {
|
|
|
|
return transport.administratorOperation(r)
|
|
|
|
}
|
|
|
|
|
2021-07-13 06:09:58 +00:00
|
|
|
volumeName := volumeIDParameter[0]
|
|
|
|
|
2021-07-19 07:43:49 +00:00
|
|
|
resourceID, err := transport.getVolumeResourceID(volumeName)
|
2020-08-20 12:29:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-05-12 04:08:01 +00:00
|
|
|
// volume browser request
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.restrictedResourceOperation(r, resourceID, volumeName, portainer.VolumeResourceControl, true)
|
2021-03-24 18:27:32 +00:00
|
|
|
case strings.HasPrefix(requestPath, "/dockerhub"):
|
2021-07-14 09:15:21 +00:00
|
|
|
requestPath, registryIdString := path.Split(r.URL.Path)
|
|
|
|
|
|
|
|
registryID, err := strconv.Atoi(registryIdString)
|
2021-03-24 18:27:32 +00:00
|
|
|
if err != nil {
|
2021-07-14 09:15:21 +00:00
|
|
|
return nil, fmt.Errorf("missing registry id: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
r.URL.Path = strings.TrimSuffix(requestPath, "/")
|
|
|
|
|
|
|
|
registry := &portainer.Registry{
|
|
|
|
Type: portainer.DockerHubRegistry,
|
|
|
|
}
|
|
|
|
|
|
|
|
if registryID != 0 {
|
|
|
|
registry, err = transport.dataStore.Registry().Registry(portainer.RegistryID(registryID))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed fetching registry: %w", err)
|
|
|
|
}
|
2021-03-24 18:27:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-14 09:15:21 +00:00
|
|
|
if registry.Type != portainer.DockerHubRegistry {
|
|
|
|
return nil, errors.New("Invalid registry type")
|
|
|
|
}
|
|
|
|
|
|
|
|
newBody, err := json.Marshal(registry)
|
2021-03-24 18:27:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Method = http.MethodPost
|
|
|
|
|
|
|
|
r.Body = ioutil.NopCloser(bytes.NewReader(newBody))
|
|
|
|
r.ContentLength = int64(len(newBody))
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return transport.executeDockerRequest(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxyConfigRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
switch requestPath := request.URL.Path; requestPath {
|
|
|
|
case "/configs/create":
|
|
|
|
return transport.decorateGenericResourceCreationOperation(request, configObjectIdentifier, portainer.ConfigResourceControl)
|
|
|
|
|
|
|
|
case "/configs":
|
|
|
|
return transport.rewriteOperation(request, transport.configListOperation)
|
|
|
|
|
|
|
|
default:
|
|
|
|
// assume /configs/{id}
|
|
|
|
configID := path.Base(requestPath)
|
|
|
|
|
|
|
|
if request.Method == http.MethodGet {
|
|
|
|
return transport.rewriteOperation(request, transport.configInspectOperation)
|
|
|
|
} else if request.Method == http.MethodDelete {
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.executeGenericResourceDeletionOperation(request, configID, configID, portainer.ConfigResourceControl)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.restrictedResourceOperation(request, configID, configID, portainer.ConfigResourceControl, false)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxyContainerRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
switch requestPath := request.URL.Path; requestPath {
|
|
|
|
case "/containers/create":
|
2020-07-22 18:38:45 +00:00
|
|
|
return transport.decorateContainerCreationOperation(request, containerObjectIdentifier, portainer.ContainerResourceControl)
|
2019-11-12 23:41:42 +00:00
|
|
|
|
|
|
|
case "/containers/prune":
|
|
|
|
return transport.administratorOperation(request)
|
|
|
|
|
|
|
|
case "/containers/json":
|
|
|
|
return transport.rewriteOperationWithLabelFiltering(request, transport.containerListOperation)
|
|
|
|
|
|
|
|
default:
|
|
|
|
// This section assumes /containers/**
|
|
|
|
if match, _ := path.Match("/containers/*/*", requestPath); match {
|
|
|
|
// Handle /containers/{id}/{action} requests
|
|
|
|
containerID := path.Base(path.Dir(requestPath))
|
|
|
|
action := path.Base(requestPath)
|
|
|
|
|
|
|
|
if action == "json" {
|
|
|
|
return transport.rewriteOperation(request, transport.containerInspectOperation)
|
|
|
|
}
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.restrictedResourceOperation(request, containerID, containerID, portainer.ContainerResourceControl, false)
|
2019-11-12 23:41:42 +00:00
|
|
|
} else if match, _ := path.Match("/containers/*", requestPath); match {
|
|
|
|
// Handle /containers/{id} requests
|
|
|
|
containerID := path.Base(requestPath)
|
|
|
|
|
|
|
|
if request.Method == http.MethodDelete {
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.executeGenericResourceDeletionOperation(request, containerID, containerID, portainer.ContainerResourceControl)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.restrictedResourceOperation(request, containerID, containerID, portainer.ContainerResourceControl, false)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
return transport.executeDockerRequest(request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxyServiceRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
switch requestPath := request.URL.Path; requestPath {
|
|
|
|
case "/services/create":
|
2020-07-29 09:10:46 +00:00
|
|
|
return transport.decorateServiceCreationOperation(request)
|
2019-11-12 23:41:42 +00:00
|
|
|
|
|
|
|
case "/services":
|
|
|
|
return transport.rewriteOperation(request, transport.serviceListOperation)
|
|
|
|
|
|
|
|
default:
|
|
|
|
// This section assumes /services/**
|
|
|
|
if match, _ := path.Match("/services/*/*", requestPath); match {
|
|
|
|
// Handle /services/{id}/{action} requests
|
|
|
|
serviceID := path.Base(path.Dir(requestPath))
|
2022-03-10 05:35:11 +00:00
|
|
|
transport.decorateRegistryAuthenticationHeader(request)
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.restrictedResourceOperation(request, serviceID, serviceID, portainer.ServiceResourceControl, false)
|
2019-11-12 23:41:42 +00:00
|
|
|
} else if match, _ := path.Match("/services/*", requestPath); match {
|
|
|
|
// Handle /services/{id} requests
|
|
|
|
serviceID := path.Base(requestPath)
|
|
|
|
|
|
|
|
switch request.Method {
|
|
|
|
case http.MethodGet:
|
|
|
|
return transport.rewriteOperation(request, transport.serviceInspectOperation)
|
|
|
|
case http.MethodDelete:
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.executeGenericResourceDeletionOperation(request, serviceID, serviceID, portainer.ServiceResourceControl)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.restrictedResourceOperation(request, serviceID, serviceID, portainer.ServiceResourceControl, false)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
return transport.executeDockerRequest(request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxyVolumeRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
switch requestPath := request.URL.Path; requestPath {
|
|
|
|
case "/volumes/create":
|
2021-07-19 07:43:49 +00:00
|
|
|
return transport.decorateVolumeResourceCreationOperation(request, portainer.VolumeResourceControl)
|
2019-11-12 23:41:42 +00:00
|
|
|
|
|
|
|
case "/volumes/prune":
|
|
|
|
return transport.administratorOperation(request)
|
|
|
|
|
|
|
|
case "/volumes":
|
|
|
|
return transport.rewriteOperation(request, transport.volumeListOperation)
|
|
|
|
|
|
|
|
default:
|
|
|
|
// assume /volumes/{name}
|
2020-07-07 00:01:18 +00:00
|
|
|
return transport.restrictedVolumeOperation(requestPath, request)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxyNetworkRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
switch requestPath := request.URL.Path; requestPath {
|
|
|
|
case "/networks/create":
|
|
|
|
return transport.decorateGenericResourceCreationOperation(request, networkObjectIdentifier, portainer.NetworkResourceControl)
|
|
|
|
|
|
|
|
case "/networks":
|
|
|
|
return transport.rewriteOperation(request, transport.networkListOperation)
|
|
|
|
|
|
|
|
default:
|
|
|
|
// assume /networks/{id}
|
|
|
|
networkID := path.Base(requestPath)
|
|
|
|
|
|
|
|
if request.Method == http.MethodGet {
|
|
|
|
return transport.rewriteOperation(request, transport.networkInspectOperation)
|
|
|
|
} else if request.Method == http.MethodDelete {
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.executeGenericResourceDeletionOperation(request, networkID, networkID, portainer.NetworkResourceControl)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.restrictedResourceOperation(request, networkID, networkID, portainer.NetworkResourceControl, false)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxySecretRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
switch requestPath := request.URL.Path; requestPath {
|
|
|
|
case "/secrets/create":
|
|
|
|
return transport.decorateGenericResourceCreationOperation(request, secretObjectIdentifier, portainer.SecretResourceControl)
|
|
|
|
|
|
|
|
case "/secrets":
|
|
|
|
return transport.rewriteOperation(request, transport.secretListOperation)
|
|
|
|
|
|
|
|
default:
|
|
|
|
// assume /secrets/{id}
|
|
|
|
secretID := path.Base(requestPath)
|
|
|
|
|
|
|
|
if request.Method == http.MethodGet {
|
|
|
|
return transport.rewriteOperation(request, transport.secretInspectOperation)
|
|
|
|
} else if request.Method == http.MethodDelete {
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.executeGenericResourceDeletionOperation(request, secretID, secretID, portainer.SecretResourceControl)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
2021-07-13 06:09:58 +00:00
|
|
|
return transport.restrictedResourceOperation(request, secretID, secretID, portainer.SecretResourceControl, false)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxyNodeRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
requestPath := request.URL.Path
|
|
|
|
|
|
|
|
// assume /nodes/{id}
|
|
|
|
if path.Base(requestPath) != "nodes" {
|
|
|
|
return transport.administratorOperation(request)
|
|
|
|
}
|
|
|
|
|
|
|
|
return transport.executeDockerRequest(request)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxySwarmRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
switch requestPath := request.URL.Path; requestPath {
|
|
|
|
case "/swarm":
|
|
|
|
return transport.rewriteOperation(request, swarmInspectOperation)
|
|
|
|
default:
|
|
|
|
// assume /swarm/{action}
|
|
|
|
return transport.administratorOperation(request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxyTaskRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
switch requestPath := request.URL.Path; requestPath {
|
|
|
|
case "/tasks":
|
|
|
|
return transport.rewriteOperation(request, transport.taskListOperation)
|
|
|
|
default:
|
|
|
|
// assume /tasks/{id}
|
|
|
|
return transport.executeDockerRequest(request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) proxyBuildRequest(request *http.Request) (*http.Response, error) {
|
2022-05-15 21:35:11 +00:00
|
|
|
err := transport.updateDefaultGitBranch(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-11-12 23:41:42 +00:00
|
|
|
return transport.interceptAndRewriteRequest(request, buildOperation)
|
|
|
|
}
|
|
|
|
|
2022-05-15 21:35:11 +00:00
|
|
|
func (transport *Transport) updateDefaultGitBranch(request *http.Request) error {
|
|
|
|
remote := request.URL.Query().Get("remote")
|
|
|
|
if strings.HasSuffix(remote, ".git") {
|
|
|
|
repositoryURL := remote[:len(remote)-4]
|
|
|
|
latestCommitID, err := transport.gitService.LatestCommitID(repositoryURL, "", "", "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
newRemote := fmt.Sprintf("%s#%s", remote, latestCommitID)
|
|
|
|
|
|
|
|
q := request.URL.Query()
|
|
|
|
q.Set("remote", newRemote)
|
|
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-12 23:41:42 +00:00
|
|
|
func (transport *Transport) proxyImageRequest(request *http.Request) (*http.Response, error) {
|
|
|
|
switch requestPath := request.URL.Path; requestPath {
|
|
|
|
case "/images/create":
|
|
|
|
return transport.replaceRegistryAuthenticationHeader(request)
|
|
|
|
default:
|
|
|
|
if path.Base(requestPath) == "push" && request.Method == http.MethodPost {
|
|
|
|
return transport.replaceRegistryAuthenticationHeader(request)
|
|
|
|
}
|
|
|
|
return transport.executeDockerRequest(request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) replaceRegistryAuthenticationHeader(request *http.Request) (*http.Response, error) {
|
2022-03-10 05:35:11 +00:00
|
|
|
transport.decorateRegistryAuthenticationHeader(request)
|
|
|
|
return transport.decorateGenericResourceCreationOperation(request, serviceObjectIdentifier, portainer.ServiceResourceControl)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) decorateRegistryAuthenticationHeader(request *http.Request) error {
|
2019-11-12 23:41:42 +00:00
|
|
|
accessContext, err := transport.createRegistryAccessContext(request)
|
|
|
|
if err != nil {
|
2022-03-10 05:35:11 +00:00
|
|
|
return err
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
originalHeader := request.Header.Get("X-Registry-Auth")
|
|
|
|
|
|
|
|
if originalHeader != "" {
|
|
|
|
|
|
|
|
decodedHeaderData, err := base64.StdEncoding.DecodeString(originalHeader)
|
|
|
|
if err != nil {
|
2022-03-10 05:35:11 +00:00
|
|
|
return err
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
2021-07-14 09:15:21 +00:00
|
|
|
var originalHeaderData portainerRegistryAuthenticationHeader
|
2019-11-12 23:41:42 +00:00
|
|
|
err = json.Unmarshal(decodedHeaderData, &originalHeaderData)
|
|
|
|
if err != nil {
|
2022-03-10 05:35:11 +00:00
|
|
|
return err
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
2022-06-15 21:53:58 +00:00
|
|
|
// delete header and exist function without error if Front End
|
|
|
|
// passes empty json. This is to restore original behavior which
|
|
|
|
// never originally passed this header
|
|
|
|
if string(decodedHeaderData) == "{}" {
|
|
|
|
request.Header.Del("X-Registry-Auth")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// only set X-Registry-Auth if registryId is defined
|
|
|
|
if originalHeaderData.RegistryId == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
authenticationHeader, err := createRegistryAuthenticationHeader(transport.dataStore, *originalHeaderData.RegistryId, accessContext)
|
2021-12-01 00:18:57 +00:00
|
|
|
if err != nil {
|
2022-03-10 05:35:11 +00:00
|
|
|
return err
|
2021-12-01 00:18:57 +00:00
|
|
|
}
|
2019-11-12 23:41:42 +00:00
|
|
|
|
|
|
|
headerData, err := json.Marshal(authenticationHeader)
|
|
|
|
if err != nil {
|
2022-03-10 05:35:11 +00:00
|
|
|
return err
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
header := base64.StdEncoding.EncodeToString(headerData)
|
|
|
|
|
|
|
|
request.Header.Set("X-Registry-Auth", header)
|
|
|
|
}
|
|
|
|
|
2022-03-10 05:35:11 +00:00
|
|
|
return nil
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
2021-07-13 06:09:58 +00:00
|
|
|
func (transport *Transport) restrictedResourceOperation(request *http.Request, resourceID string, dockerResourceID string, resourceType portainer.ResourceControlType, volumeBrowseRestrictionCheck bool) (*http.Response, error) {
|
2019-11-12 23:41:42 +00:00
|
|
|
var err error
|
|
|
|
tokenData, err := security.RetrieveTokenData(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if tokenData.Role != portainer.AdministratorRole {
|
|
|
|
if volumeBrowseRestrictionCheck {
|
2021-02-09 08:09:06 +00:00
|
|
|
securitySettings, err := transport.fetchEndpointSecuritySettings()
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-02-09 08:09:06 +00:00
|
|
|
if !securitySettings.AllowVolumeBrowserForRegularUsers {
|
2021-07-14 09:15:21 +00:00
|
|
|
return utils.WriteAccessDeniedResponse()
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-20 05:23:15 +00:00
|
|
|
teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
userTeamIDs := make([]portainer.TeamID, 0)
|
|
|
|
for _, membership := range teamMemberships {
|
|
|
|
userTeamIDs = append(userTeamIDs, membership.TeamID)
|
|
|
|
}
|
|
|
|
|
2020-05-20 05:23:15 +00:00
|
|
|
resourceControls, err := transport.dataStore.ResourceControl().ResourceControls()
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-06-16 07:58:16 +00:00
|
|
|
resourceControl := authorization.GetResourceControlByResourceIDAndType(resourceID, resourceType, resourceControls)
|
2019-11-12 23:41:42 +00:00
|
|
|
if resourceControl == nil {
|
|
|
|
agentTargetHeader := request.Header.Get(portainer.PortainerAgentTargetHeader)
|
|
|
|
|
2021-07-13 06:09:58 +00:00
|
|
|
if dockerResourceID == "" {
|
|
|
|
dockerResourceID = resourceID
|
|
|
|
}
|
|
|
|
|
2019-11-12 23:41:42 +00:00
|
|
|
// This resource was created outside of portainer,
|
|
|
|
// is part of a Docker service or part of a Docker Swarm/Compose stack.
|
2021-07-13 06:09:58 +00:00
|
|
|
inheritedResourceControl, err := transport.getInheritedResourceControlFromServiceOrStack(dockerResourceID, agentTargetHeader, resourceType, resourceControls)
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-06-16 07:58:16 +00:00
|
|
|
if inheritedResourceControl == nil || !authorization.UserCanAccessResource(tokenData.ID, userTeamIDs, inheritedResourceControl) {
|
2021-07-14 09:15:21 +00:00
|
|
|
return utils.WriteAccessDeniedResponse()
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-16 07:58:16 +00:00
|
|
|
if resourceControl != nil && !authorization.UserCanAccessResource(tokenData.ID, userTeamIDs, resourceControl) {
|
2021-07-14 09:15:21 +00:00
|
|
|
return utils.WriteAccessDeniedResponse()
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return transport.executeDockerRequest(request)
|
|
|
|
}
|
|
|
|
|
|
|
|
// rewriteOperationWithLabelFiltering will create a new operation context with data that will be used
|
|
|
|
// to decorate the original request's response as well as retrieve all the black listed labels
|
|
|
|
// to filter the resources.
|
|
|
|
func (transport *Transport) rewriteOperationWithLabelFiltering(request *http.Request, operation restrictedOperationRequest) (*http.Response, error) {
|
|
|
|
operationContext, err := transport.createOperationContext(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-05-20 05:23:15 +00:00
|
|
|
settings, err := transport.dataStore.Settings().Settings()
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
executor := &operationExecutor{
|
|
|
|
operationContext: operationContext,
|
|
|
|
labelBlackList: settings.BlackListedLabels,
|
|
|
|
}
|
|
|
|
|
|
|
|
return transport.executeRequestAndRewriteResponse(request, operation, executor)
|
|
|
|
}
|
|
|
|
|
|
|
|
// rewriteOperation will create a new operation context with data that will be used
|
|
|
|
// to decorate the original request's response.
|
|
|
|
func (transport *Transport) rewriteOperation(request *http.Request, operation restrictedOperationRequest) (*http.Response, error) {
|
|
|
|
operationContext, err := transport.createOperationContext(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
executor := &operationExecutor{
|
|
|
|
operationContext: operationContext,
|
|
|
|
}
|
|
|
|
|
|
|
|
return transport.executeRequestAndRewriteResponse(request, operation, executor)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) interceptAndRewriteRequest(request *http.Request, operation operationRequest) (*http.Response, error) {
|
|
|
|
err := operation(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return transport.executeDockerRequest(request)
|
|
|
|
}
|
|
|
|
|
|
|
|
// decorateGenericResourceCreationResponse extracts the response as a JSON object, extracts the resource identifier from that object based
|
|
|
|
// on the resourceIdentifierAttribute parameter then generate a new resource control associated to that resource
|
|
|
|
// with a random token and rewrites the response by decorating the original response with a ResourceControl object.
|
|
|
|
// The generic Docker API response format is JSON object:
|
2020-01-29 04:36:28 +00:00
|
|
|
// https://docs.docker.com/engine/api/v1.37/#operation/ContainerCreate
|
|
|
|
// https://docs.docker.com/engine/api/v1.37/#operation/NetworkCreate
|
|
|
|
// https://docs.docker.com/engine/api/v1.37/#operation/VolumeCreate
|
|
|
|
// https://docs.docker.com/engine/api/v1.37/#operation/ServiceCreate
|
|
|
|
// https://docs.docker.com/engine/api/v1.37/#operation/SecretCreate
|
|
|
|
// https://docs.docker.com/engine/api/v1.37/#operation/ConfigCreate
|
2019-11-12 23:41:42 +00:00
|
|
|
func (transport *Transport) decorateGenericResourceCreationResponse(response *http.Response, resourceIdentifierAttribute string, resourceType portainer.ResourceControlType, userID portainer.UserID) error {
|
2021-07-14 09:15:21 +00:00
|
|
|
responseObject, err := utils.GetResponseAsJSONObject(response)
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if responseObject[resourceIdentifierAttribute] == nil {
|
|
|
|
log.Printf("[ERROR] [proxy,docker]")
|
|
|
|
return errors.New("missing identifier in Docker resource creation response")
|
|
|
|
}
|
|
|
|
|
|
|
|
resourceID := responseObject[resourceIdentifierAttribute].(string)
|
|
|
|
|
|
|
|
resourceControl, err := transport.createPrivateResourceControl(resourceID, resourceType, userID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
responseObject = decorateObject(responseObject, resourceControl)
|
|
|
|
|
2021-07-14 09:15:21 +00:00
|
|
|
return utils.RewriteResponse(response, responseObject, http.StatusOK)
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) decorateGenericResourceCreationOperation(request *http.Request, resourceIdentifierAttribute string, resourceType portainer.ResourceControlType) (*http.Response, error) {
|
|
|
|
tokenData, err := security.RetrieveTokenData(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response, err := transport.executeDockerRequest(request)
|
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if response.StatusCode == http.StatusCreated {
|
|
|
|
err = transport.decorateGenericResourceCreationResponse(response, resourceIdentifierAttribute, resourceType, tokenData.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
2021-07-13 06:09:58 +00:00
|
|
|
func (transport *Transport) executeGenericResourceDeletionOperation(request *http.Request, resourceIdentifierAttribute string, volumeName string, resourceType portainer.ResourceControlType) (*http.Response, error) {
|
|
|
|
response, err := transport.restrictedResourceOperation(request, resourceIdentifierAttribute, volumeName, resourceType, false)
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
2020-12-10 07:31:31 +00:00
|
|
|
if response.StatusCode == http.StatusNoContent || response.StatusCode == http.StatusOK {
|
|
|
|
resourceControl, err := transport.dataStore.ResourceControl().ResourceControlByResourceIDAndType(resourceIdentifierAttribute, resourceType)
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
2022-01-23 07:17:31 +00:00
|
|
|
if err == dataerrors.ErrObjectNotFound {
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
2019-11-12 23:41:42 +00:00
|
|
|
return response, err
|
|
|
|
}
|
2020-12-10 07:31:31 +00:00
|
|
|
|
|
|
|
if resourceControl != nil {
|
|
|
|
err = transport.dataStore.ResourceControl().DeleteResourceControl(resourceControl.ID)
|
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
}
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) executeRequestAndRewriteResponse(request *http.Request, operation restrictedOperationRequest, executor *operationExecutor) (*http.Response, error) {
|
|
|
|
response, err := transport.executeDockerRequest(request)
|
|
|
|
if err != nil {
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = operation(response, executor)
|
|
|
|
return response, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// administratorOperation ensures that the user has administrator privileges
|
|
|
|
// before executing the original request.
|
|
|
|
func (transport *Transport) administratorOperation(request *http.Request) (*http.Response, error) {
|
|
|
|
tokenData, err := security.RetrieveTokenData(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if tokenData.Role != portainer.AdministratorRole {
|
2021-07-14 09:15:21 +00:00
|
|
|
return utils.WriteAccessDeniedResponse()
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return transport.executeDockerRequest(request)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) createRegistryAccessContext(request *http.Request) (*registryAccessContext, error) {
|
|
|
|
tokenData, err := security.RetrieveTokenData(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
accessContext := ®istryAccessContext{
|
2021-07-14 09:15:21 +00:00
|
|
|
isAdmin: true,
|
|
|
|
endpointID: transport.endpoint.ID,
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
2021-07-14 09:15:21 +00:00
|
|
|
user, err := transport.dataStore.User().User(tokenData.ID)
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-07-14 09:15:21 +00:00
|
|
|
accessContext.user = user
|
2019-11-12 23:41:42 +00:00
|
|
|
|
2020-05-20 05:23:15 +00:00
|
|
|
registries, err := transport.dataStore.Registry().Registries()
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
accessContext.registries = registries
|
|
|
|
|
2021-07-14 09:15:21 +00:00
|
|
|
if user.Role != portainer.AdministratorRole {
|
2019-11-12 23:41:42 +00:00
|
|
|
accessContext.isAdmin = false
|
|
|
|
|
2020-05-20 05:23:15 +00:00
|
|
|
teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
accessContext.teamMemberships = teamMemberships
|
|
|
|
}
|
|
|
|
|
|
|
|
return accessContext, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (transport *Transport) createOperationContext(request *http.Request) (*restrictedDockerOperationContext, error) {
|
|
|
|
var err error
|
|
|
|
tokenData, err := security.RetrieveTokenData(request)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-05-20 05:23:15 +00:00
|
|
|
resourceControls, err := transport.dataStore.ResourceControl().ResourceControls()
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
operationContext := &restrictedDockerOperationContext{
|
2020-08-24 23:04:51 +00:00
|
|
|
isAdmin: true,
|
|
|
|
userID: tokenData.ID,
|
|
|
|
resourceControls: resourceControls,
|
2019-11-12 23:41:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if tokenData.Role != portainer.AdministratorRole {
|
|
|
|
operationContext.isAdmin = false
|
|
|
|
|
2020-05-20 05:23:15 +00:00
|
|
|
teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
|
2019-11-12 23:41:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
userTeamIDs := make([]portainer.TeamID, 0)
|
|
|
|
for _, membership := range teamMemberships {
|
|
|
|
userTeamIDs = append(userTeamIDs, membership.TeamID)
|
|
|
|
}
|
|
|
|
operationContext.userTeamIDs = userTeamIDs
|
|
|
|
}
|
|
|
|
|
|
|
|
return operationContext, nil
|
|
|
|
}
|
2020-07-29 09:10:46 +00:00
|
|
|
|
|
|
|
func (transport *Transport) isAdminOrEndpointAdmin(request *http.Request) (bool, error) {
|
|
|
|
tokenData, err := security.RetrieveTokenData(request)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2020-08-11 05:41:37 +00:00
|
|
|
return tokenData.Role == portainer.AdministratorRole, nil
|
2020-07-29 09:10:46 +00:00
|
|
|
}
|
2021-02-09 08:09:06 +00:00
|
|
|
|
|
|
|
func (transport *Transport) fetchEndpointSecuritySettings() (*portainer.EndpointSecuritySettings, error) {
|
|
|
|
endpoint, err := transport.dataStore.Endpoint().Endpoint(portainer.EndpointID(transport.endpoint.ID))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &endpoint.SecuritySettings, nil
|
|
|
|
}
|