124 lines
3.9 KiB
Go
124 lines
3.9 KiB
Go
package docker
|
|
|
|
import (
|
|
"bytes"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
portainer "github.com/portainer/portainer/api"
|
|
"github.com/portainer/portainer/api/dataservices"
|
|
"github.com/portainer/portainer/api/datastore"
|
|
"github.com/portainer/portainer/api/http/security"
|
|
|
|
"github.com/segmentio/encoding/json"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestDecorateContainerCreationOperation_BindMounts(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
admin := portainer.User{ID: 1, Username: "admin", Role: portainer.AdministratorRole}
|
|
regularUser := portainer.User{ID: 2, Username: "user", Role: portainer.StandardUserRole}
|
|
|
|
_, ds := datastore.MustNewTestStore(t, true, false)
|
|
|
|
err := ds.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
|
err := tx.User().Create(&admin)
|
|
require.NoError(t, err)
|
|
|
|
err = tx.User().Create(®ularUser)
|
|
require.NoError(t, err)
|
|
|
|
err = tx.Endpoint().Create(&portainer.Endpoint{
|
|
ID: 1,
|
|
Name: "test",
|
|
SecuritySettings: portainer.EndpointSecuritySettings{
|
|
AllowBindMountsForRegularUsers: false,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
srv, version := mockDockerAPIServer(t, RoutesDefinition{
|
|
{http.MethodPost, "/containers/create"}: map[string]any{"Id": "abc123", "Warnings": []any{}},
|
|
})
|
|
defer srv.Close()
|
|
|
|
transport := &Transport{
|
|
endpoint: &portainer.Endpoint{ID: 1, URL: srv.URL},
|
|
dataStore: ds,
|
|
HTTPTransport: &http.Transport{},
|
|
}
|
|
|
|
adminToken := portainer.TokenData{ID: admin.ID, Username: admin.Username, Role: admin.Role}
|
|
userToken := portainer.TokenData{ID: regularUser.ID, Username: regularUser.Username, Role: regularUser.Role}
|
|
|
|
makeRequest := func(token portainer.TokenData, body any) *http.Request {
|
|
bodyBytes, err := json.Marshal(body)
|
|
require.NoError(t, err)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, srv.URL+"/v"+version+"/containers/create", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(security.StoreTokenData(req, &token))
|
|
|
|
return req
|
|
}
|
|
|
|
// Admin bypasses security checks
|
|
req := makeRequest(adminToken, map[string]any{
|
|
"HostConfig": map[string]any{
|
|
"Mounts": []map[string]any{{"Type": "bind", "Source": "/", "Target": "/host"}},
|
|
},
|
|
})
|
|
resp, err := transport.decorateContainerCreationOperation(req, containerObjectIdentifier, portainer.ContainerResourceControl)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
|
|
err = resp.Body.Close()
|
|
require.NoError(t, err)
|
|
|
|
// HostConfig.Binds with an absolute path is blocked for regular users
|
|
req = makeRequest(userToken, map[string]any{
|
|
"HostConfig": map[string]any{
|
|
"Binds": []string{"/:/host:ro"},
|
|
},
|
|
})
|
|
resp, err = transport.decorateContainerCreationOperation(req, containerObjectIdentifier, portainer.ContainerResourceControl)
|
|
require.ErrorIs(t, err, ErrBindMountsForbidden)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
|
|
|
err = resp.Body.Close()
|
|
require.NoError(t, err)
|
|
|
|
// HostConfig.Mounts with type bind is blocked for regular users
|
|
req = makeRequest(userToken, map[string]any{
|
|
"HostConfig": map[string]any{
|
|
"Mounts": []map[string]any{{"Type": "bind", "Source": "/", "Target": "/host"}},
|
|
},
|
|
})
|
|
resp, err = transport.decorateContainerCreationOperation(req, containerObjectIdentifier, portainer.ContainerResourceControl)
|
|
require.ErrorIs(t, err, ErrBindMountsForbidden)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
|
|
|
err = resp.Body.Close()
|
|
require.NoError(t, err)
|
|
|
|
// HostConfig.Mounts with a non-bind type is allowed for regular users
|
|
req = makeRequest(userToken, map[string]any{
|
|
"HostConfig": map[string]any{
|
|
"Mounts": []map[string]any{{"Type": "volume", "Source": "myvolume", "Target": "/data"}},
|
|
},
|
|
})
|
|
resp, err = transport.decorateContainerCreationOperation(req, containerObjectIdentifier, portainer.ContainerResourceControl)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
|
|
err = resp.Body.Close()
|
|
require.NoError(t, err)
|
|
}
|