Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecf52616e2 | ||
|
|
69bc599b5b | ||
|
|
e58b019ffa | ||
|
|
1fc4e7bddb | ||
|
|
2cabfd574c | ||
|
|
3b946d84ac | ||
|
|
28abe55179 | ||
|
|
e31365c6a5 | ||
|
|
bedb4fc7f4 | ||
|
|
8f05ba77b4 | ||
|
|
d03e22e26e | ||
|
|
ec667a19a0 | ||
|
|
8afe1ac37b | ||
|
|
9dc3188cc0 | ||
|
|
9cf014adab |
43
api/chisel/server.go
Normal file
43
api/chisel/server.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package chisel
|
||||
|
||||
import (
|
||||
chserver "github.com/jpillora/chisel/server"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
address string
|
||||
port string
|
||||
fingerprint string
|
||||
}
|
||||
|
||||
func NewServer(address string, port string) *Server {
|
||||
return &Server{
|
||||
address: address,
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the reverse tunnel server
|
||||
func (server *Server) Start() error {
|
||||
|
||||
// TODO: keyseed management (persistence)
|
||||
// + auth management
|
||||
// Consider multiple users for auth?
|
||||
config := &chserver.Config{
|
||||
Reverse: true,
|
||||
KeySeed: "keyseedexample",
|
||||
Auth: "agent@randomstring",
|
||||
}
|
||||
|
||||
chiselServer, err := chserver.NewServer(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server.fingerprint = chiselServer.GetFingerprint()
|
||||
return chiselServer.Start(server.address, server.port)
|
||||
}
|
||||
|
||||
func (server *Server) GetFingerprint() string {
|
||||
return server.fingerprint
|
||||
}
|
||||
@@ -33,6 +33,8 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
||||
|
||||
flags := &portainer.CLIFlags{
|
||||
Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(),
|
||||
TunnelAddr: kingpin.Flag("tunnel-addr", "Address to serve the tunnel server").Default(defaultTunnelServerAddress).String(),
|
||||
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
|
||||
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
|
||||
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
|
||||
EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(),
|
||||
|
||||
@@ -3,21 +3,23 @@
|
||||
package cli
|
||||
|
||||
const (
|
||||
defaultBindAddress = ":9000"
|
||||
defaultDataDirectory = "/data"
|
||||
defaultAssetsDirectory = "./"
|
||||
defaultNoAuth = "false"
|
||||
defaultNoAnalytics = "false"
|
||||
defaultTLS = "false"
|
||||
defaultTLSSkipVerify = "false"
|
||||
defaultTLSCACertPath = "/certs/ca.pem"
|
||||
defaultTLSCertPath = "/certs/cert.pem"
|
||||
defaultTLSKeyPath = "/certs/key.pem"
|
||||
defaultSSL = "false"
|
||||
defaultSSLCertPath = "/certs/portainer.crt"
|
||||
defaultSSLKeyPath = "/certs/portainer.key"
|
||||
defaultSyncInterval = "60s"
|
||||
defaultSnapshot = "true"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultTemplateFile = "/templates.json"
|
||||
defaultBindAddress = ":9000"
|
||||
defaultTunnelServerAddress = "0.0.0.0"
|
||||
defaultTunnelServerPort = "8000"
|
||||
defaultDataDirectory = "/data"
|
||||
defaultAssetsDirectory = "./"
|
||||
defaultNoAuth = "false"
|
||||
defaultNoAnalytics = "false"
|
||||
defaultTLS = "false"
|
||||
defaultTLSSkipVerify = "false"
|
||||
defaultTLSCACertPath = "/certs/ca.pem"
|
||||
defaultTLSCertPath = "/certs/cert.pem"
|
||||
defaultTLSKeyPath = "/certs/key.pem"
|
||||
defaultSSL = "false"
|
||||
defaultSSLCertPath = "/certs/portainer.crt"
|
||||
defaultSSLKeyPath = "/certs/portainer.key"
|
||||
defaultSyncInterval = "60s"
|
||||
defaultSnapshot = "true"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultTemplateFile = "/templates.json"
|
||||
)
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
package cli
|
||||
|
||||
const (
|
||||
defaultBindAddress = ":9000"
|
||||
defaultDataDirectory = "C:\\data"
|
||||
defaultAssetsDirectory = "./"
|
||||
defaultNoAuth = "false"
|
||||
defaultNoAnalytics = "false"
|
||||
defaultTLS = "false"
|
||||
defaultTLSSkipVerify = "false"
|
||||
defaultTLSCACertPath = "C:\\certs\\ca.pem"
|
||||
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
||||
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
||||
defaultSSL = "false"
|
||||
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
||||
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
||||
defaultSyncInterval = "60s"
|
||||
defaultSnapshot = "true"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultTemplateFile = "/templates.json"
|
||||
defaultBindAddress = ":9000"
|
||||
defaultTunnelServerAddress = "0.0.0.0"
|
||||
defaultTunnelServerPort = "8000"
|
||||
defaultDataDirectory = "C:\\data"
|
||||
defaultAssetsDirectory = "./"
|
||||
defaultNoAuth = "false"
|
||||
defaultNoAnalytics = "false"
|
||||
defaultTLS = "false"
|
||||
defaultTLSSkipVerify = "false"
|
||||
defaultTLSCACertPath = "C:\\certs\\ca.pem"
|
||||
defaultTLSCertPath = "C:\\certs\\cert.pem"
|
||||
defaultTLSKeyPath = "C:\\certs\\key.pem"
|
||||
defaultSSL = "false"
|
||||
defaultSSLCertPath = "C:\\certs\\portainer.crt"
|
||||
defaultSSLKeyPath = "C:\\certs\\portainer.key"
|
||||
defaultSyncInterval = "60s"
|
||||
defaultSnapshot = "true"
|
||||
defaultSnapshotInterval = "5m"
|
||||
defaultTemplateFile = "/templates.json"
|
||||
)
|
||||
|
||||
@@ -2,12 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt"
|
||||
"github.com/portainer/portainer/api/chisel"
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
"github.com/portainer/portainer/api/cron"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
@@ -20,8 +22,6 @@ import (
|
||||
"github.com/portainer/portainer/api/jwt"
|
||||
"github.com/portainer/portainer/api/ldap"
|
||||
"github.com/portainer/portainer/api/libcompose"
|
||||
|
||||
"log"
|
||||
)
|
||||
|
||||
func initCLI() *portainer.CLIFlags {
|
||||
@@ -658,44 +658,51 @@ func main() {
|
||||
go terminateIfNoAdminCreated(store.UserService)
|
||||
}
|
||||
|
||||
var tunnelServer portainer.TunnelServer = chisel.NewServer(*flags.TunnelAddr, *flags.TunnelPort)
|
||||
err = tunnelServer.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var server portainer.Server = &http.Server{
|
||||
Status: applicationStatus,
|
||||
BindAddress: *flags.Addr,
|
||||
AssetsPath: *flags.Assets,
|
||||
AuthDisabled: *flags.NoAuth,
|
||||
EndpointManagement: endpointManagement,
|
||||
RoleService: store.RoleService,
|
||||
UserService: store.UserService,
|
||||
TeamService: store.TeamService,
|
||||
TeamMembershipService: store.TeamMembershipService,
|
||||
EndpointService: store.EndpointService,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
ExtensionService: store.ExtensionService,
|
||||
ResourceControlService: store.ResourceControlService,
|
||||
SettingsService: store.SettingsService,
|
||||
RegistryService: store.RegistryService,
|
||||
DockerHubService: store.DockerHubService,
|
||||
StackService: store.StackService,
|
||||
ScheduleService: store.ScheduleService,
|
||||
TagService: store.TagService,
|
||||
TemplateService: store.TemplateService,
|
||||
WebhookService: store.WebhookService,
|
||||
SwarmStackManager: swarmStackManager,
|
||||
ComposeStackManager: composeStackManager,
|
||||
ExtensionManager: extensionManager,
|
||||
CryptoService: cryptoService,
|
||||
JWTService: jwtService,
|
||||
FileService: fileService,
|
||||
LDAPService: ldapService,
|
||||
GitService: gitService,
|
||||
SignatureService: digitalSignatureService,
|
||||
JobScheduler: jobScheduler,
|
||||
Snapshotter: snapshotter,
|
||||
SSL: *flags.SSL,
|
||||
SSLCert: *flags.SSLCert,
|
||||
SSLKey: *flags.SSLKey,
|
||||
DockerClientFactory: clientFactory,
|
||||
JobService: jobService,
|
||||
TunnelServerFingerprint: tunnelServer.GetFingerprint(),
|
||||
Status: applicationStatus,
|
||||
BindAddress: *flags.Addr,
|
||||
AssetsPath: *flags.Assets,
|
||||
AuthDisabled: *flags.NoAuth,
|
||||
EndpointManagement: endpointManagement,
|
||||
RoleService: store.RoleService,
|
||||
UserService: store.UserService,
|
||||
TeamService: store.TeamService,
|
||||
TeamMembershipService: store.TeamMembershipService,
|
||||
EndpointService: store.EndpointService,
|
||||
EndpointGroupService: store.EndpointGroupService,
|
||||
ExtensionService: store.ExtensionService,
|
||||
ResourceControlService: store.ResourceControlService,
|
||||
SettingsService: store.SettingsService,
|
||||
RegistryService: store.RegistryService,
|
||||
DockerHubService: store.DockerHubService,
|
||||
StackService: store.StackService,
|
||||
ScheduleService: store.ScheduleService,
|
||||
TagService: store.TagService,
|
||||
TemplateService: store.TemplateService,
|
||||
WebhookService: store.WebhookService,
|
||||
SwarmStackManager: swarmStackManager,
|
||||
ComposeStackManager: composeStackManager,
|
||||
ExtensionManager: extensionManager,
|
||||
CryptoService: cryptoService,
|
||||
JWTService: jwtService,
|
||||
FileService: fileService,
|
||||
LDAPService: ldapService,
|
||||
GitService: gitService,
|
||||
SignatureService: digitalSignatureService,
|
||||
JobScheduler: jobScheduler,
|
||||
Snapshotter: snapshotter,
|
||||
SSL: *flags.SSL,
|
||||
SSLCert: *flags.SSLCert,
|
||||
SSLKey: *flags.SSLKey,
|
||||
DockerClientFactory: clientFactory,
|
||||
JobService: jobService,
|
||||
}
|
||||
|
||||
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)
|
||||
|
||||
@@ -24,7 +24,7 @@ func (handler *Handler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http.
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
if endpoint.Status == portainer.EndpointStatusDown {
|
||||
if endpoint.Type != 4 && endpoint.Status == portainer.EndpointStatusDown {
|
||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to query endpoint", errors.New("Endpoint is down")}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package endpoints
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
@@ -41,7 +44,7 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||
|
||||
endpointType, err := request.RetrieveNumericMultiPartFormValue(r, "EndpointType", false)
|
||||
if err != nil || endpointType == 0 {
|
||||
return portainer.Error("Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment) or 3 (Azure environment)")
|
||||
return portainer.Error("Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment), 3 (Azure environment) or 4 (Edge Agent environment)")
|
||||
}
|
||||
payload.EndpointType = endpointType
|
||||
|
||||
@@ -149,6 +152,8 @@ func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *
|
||||
func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||
if portainer.EndpointType(payload.EndpointType) == portainer.AzureEnvironment {
|
||||
return handler.createAzureEndpoint(payload)
|
||||
} else if portainer.EndpointType(payload.EndpointType) == portainer.EdgeAgentEnvironment {
|
||||
return handler.createEdgeAgentEndpoint(payload)
|
||||
}
|
||||
|
||||
if payload.TLS {
|
||||
@@ -195,6 +200,63 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// TODO: relocate in a service
|
||||
// must be unique (e.g. not used / referenced)
|
||||
func randomInt(min, max int) int {
|
||||
// should be randomize at service creation time?
|
||||
// if not seeded, will always get same port order
|
||||
// might not be a problem and maybe not required
|
||||
//rand.Seed(time.Now().UnixNano())
|
||||
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
|
||||
func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||
endpointType := portainer.EdgeAgentEnvironment
|
||||
endpointID := handler.EndpointService.GetNextIdentifier()
|
||||
|
||||
// get random port
|
||||
// Dynamic ports (also called private ports) are 49152 to 65535.
|
||||
// TODO: register this port somewhere
|
||||
portnumber := randomInt(49152, 65535)
|
||||
|
||||
keyInformation := []string{
|
||||
strings.TrimPrefix(payload.URL, "tcp://"),
|
||||
"8000",
|
||||
handler.TunnelServerFingerprint,
|
||||
strconv.Itoa(portnumber),
|
||||
"agent:randomstring",
|
||||
}
|
||||
|
||||
key := strings.Join(keyInformation, "|")
|
||||
encodedKey := base64.RawStdEncoding.EncodeToString([]byte(key))
|
||||
|
||||
endpoint := &portainer.Endpoint{
|
||||
ID: portainer.EndpointID(endpointID),
|
||||
Name: payload.Name,
|
||||
URL: "tcp://localhost:" + strconv.Itoa(portnumber),
|
||||
Type: endpointType,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
},
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
Tags: payload.Tags,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
EdgeKey: string(encodedKey),
|
||||
}
|
||||
|
||||
err := handler.EndpointService.CreateEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err}
|
||||
}
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||
endpointType := portainer.DockerEnvironment
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ type Handler struct {
|
||||
ProxyManager *proxy.Manager
|
||||
Snapshotter portainer.Snapshotter
|
||||
JobService portainer.JobService
|
||||
// TODO: figure out a way to manage this (service?)
|
||||
TunnelServerFingerprint string
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage endpoint operations.
|
||||
|
||||
@@ -68,7 +68,7 @@ func (handler *Handler) websocketExec(w http.ResponseWriter, r *http.Request) *h
|
||||
func (handler *Handler) handleExecRequest(w http.ResponseWriter, r *http.Request, params *webSocketRequestParams) error {
|
||||
r.Header.Del("Origin")
|
||||
|
||||
if params.nodeName != "" || params.endpoint.Type == portainer.AgentOnDockerEnvironment {
|
||||
if params.nodeName != "" || (params.endpoint.Type == portainer.AgentOnDockerEnvironment || params.endpoint.Type == portainer.EdgeAgentEnvironment) {
|
||||
return handler.proxyWebsocketRequest(w, r, params)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/go-winio"
|
||||
"net"
|
||||
"net/http"
|
||||
"github.com/Microsoft/go-winio"
|
||||
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
|
||||
@@ -39,44 +39,45 @@ import (
|
||||
|
||||
// Server implements the portainer.Server interface
|
||||
type Server struct {
|
||||
BindAddress string
|
||||
AssetsPath string
|
||||
AuthDisabled bool
|
||||
EndpointManagement bool
|
||||
Status *portainer.Status
|
||||
ExtensionManager portainer.ExtensionManager
|
||||
ComposeStackManager portainer.ComposeStackManager
|
||||
CryptoService portainer.CryptoService
|
||||
SignatureService portainer.DigitalSignatureService
|
||||
JobScheduler portainer.JobScheduler
|
||||
Snapshotter portainer.Snapshotter
|
||||
RoleService portainer.RoleService
|
||||
DockerHubService portainer.DockerHubService
|
||||
EndpointService portainer.EndpointService
|
||||
EndpointGroupService portainer.EndpointGroupService
|
||||
FileService portainer.FileService
|
||||
GitService portainer.GitService
|
||||
JWTService portainer.JWTService
|
||||
LDAPService portainer.LDAPService
|
||||
ExtensionService portainer.ExtensionService
|
||||
RegistryService portainer.RegistryService
|
||||
ResourceControlService portainer.ResourceControlService
|
||||
ScheduleService portainer.ScheduleService
|
||||
SettingsService portainer.SettingsService
|
||||
StackService portainer.StackService
|
||||
SwarmStackManager portainer.SwarmStackManager
|
||||
TagService portainer.TagService
|
||||
TeamService portainer.TeamService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
TemplateService portainer.TemplateService
|
||||
UserService portainer.UserService
|
||||
WebhookService portainer.WebhookService
|
||||
Handler *handler.Handler
|
||||
SSL bool
|
||||
SSLCert string
|
||||
SSLKey string
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
JobService portainer.JobService
|
||||
BindAddress string
|
||||
AssetsPath string
|
||||
TunnelServerFingerprint string
|
||||
AuthDisabled bool
|
||||
EndpointManagement bool
|
||||
Status *portainer.Status
|
||||
ExtensionManager portainer.ExtensionManager
|
||||
ComposeStackManager portainer.ComposeStackManager
|
||||
CryptoService portainer.CryptoService
|
||||
SignatureService portainer.DigitalSignatureService
|
||||
JobScheduler portainer.JobScheduler
|
||||
Snapshotter portainer.Snapshotter
|
||||
RoleService portainer.RoleService
|
||||
DockerHubService portainer.DockerHubService
|
||||
EndpointService portainer.EndpointService
|
||||
EndpointGroupService portainer.EndpointGroupService
|
||||
FileService portainer.FileService
|
||||
GitService portainer.GitService
|
||||
JWTService portainer.JWTService
|
||||
LDAPService portainer.LDAPService
|
||||
ExtensionService portainer.ExtensionService
|
||||
RegistryService portainer.RegistryService
|
||||
ResourceControlService portainer.ResourceControlService
|
||||
ScheduleService portainer.ScheduleService
|
||||
SettingsService portainer.SettingsService
|
||||
StackService portainer.StackService
|
||||
SwarmStackManager portainer.SwarmStackManager
|
||||
TagService portainer.TagService
|
||||
TeamService portainer.TeamService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
TemplateService portainer.TemplateService
|
||||
UserService portainer.UserService
|
||||
WebhookService portainer.WebhookService
|
||||
Handler *handler.Handler
|
||||
SSL bool
|
||||
SSLCert string
|
||||
SSLKey string
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
JobService portainer.JobService
|
||||
}
|
||||
|
||||
// Start starts the HTTP server
|
||||
@@ -132,6 +133,7 @@ func (server *Server) Start() error {
|
||||
endpointHandler.ProxyManager = proxyManager
|
||||
endpointHandler.Snapshotter = server.Snapshotter
|
||||
endpointHandler.JobService = server.JobService
|
||||
endpointHandler.TunnelServerFingerprint = server.TunnelServerFingerprint
|
||||
|
||||
var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer)
|
||||
endpointGroupHandler.EndpointGroupService = server.EndpointGroupService
|
||||
|
||||
@@ -10,6 +10,8 @@ type (
|
||||
// CLIFlags represents the available flags on the CLI
|
||||
CLIFlags struct {
|
||||
Addr *string
|
||||
TunnelAddr *string
|
||||
TunnelPort *string
|
||||
AdminPassword *string
|
||||
AdminPasswordFile *string
|
||||
Assets *string
|
||||
@@ -250,7 +252,7 @@ type (
|
||||
Snapshots []Snapshot `json:"Snapshots"`
|
||||
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
|
||||
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
|
||||
|
||||
EdgeKey string
|
||||
// Deprecated fields
|
||||
// Deprecated in DBVersion == 4
|
||||
TLS bool `json:"TLS,omitempty"`
|
||||
@@ -594,6 +596,13 @@ type (
|
||||
Start() error
|
||||
}
|
||||
|
||||
// Tunnel server defines the interface for the reverse tunneling server used
|
||||
// with Edge agents.
|
||||
TunnelServer interface {
|
||||
Start() error
|
||||
GetFingerprint() string
|
||||
}
|
||||
|
||||
// UserService represents a service for managing user data
|
||||
UserService interface {
|
||||
User(ID UserID) (*User, error)
|
||||
@@ -951,6 +960,8 @@ const (
|
||||
AgentOnDockerEnvironment
|
||||
// AzureEnvironment represents an endpoint connected to an Azure environment
|
||||
AzureEnvironment
|
||||
// EdgeAgentEnvironment represents an endpoint connected to an Edge agent
|
||||
EdgeAgentEnvironment
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -13,7 +13,7 @@ angular.module('portainer.docker')
|
||||
agentProxy: false
|
||||
};
|
||||
|
||||
if (type === 2) {
|
||||
if (type === 2 || type === 4) {
|
||||
mode.agentProxy = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,8 @@
|
||||
{{ item.Type | endpointtypename }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ item.URL | stripprotocol }}</td>
|
||||
<td ng-if="item.Type !== 4">{{ item.URL | stripprotocol }}</td>
|
||||
<td ng-if="item.Type === 4">-</td>
|
||||
<td>{{ item.GroupName }}</td>
|
||||
<td>
|
||||
<a ui-sref="portainer.endpoints.endpoint.access({id: item.Id})" ng-if="$ctrl.accessManagement">
|
||||
|
||||
@@ -124,6 +124,8 @@ angular.module('portainer.app')
|
||||
return 'Agent';
|
||||
} else if (type === 3) {
|
||||
return 'Azure ACI';
|
||||
} else if (type === 4) {
|
||||
return 'Edge Agent';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
@@ -133,6 +135,8 @@ angular.module('portainer.app')
|
||||
return function (type) {
|
||||
if (type === 3) {
|
||||
return 'fab fa-microsoft';
|
||||
} else if (type === 4) {
|
||||
return 'fa fa-cloud';
|
||||
}
|
||||
return 'fab fa-docker';
|
||||
};
|
||||
|
||||
@@ -56,6 +56,15 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
||||
addEndpoint(name, 2, URL, publicURL, groupId, tags, true, true, true, null, null, null);
|
||||
};
|
||||
|
||||
$scope.addEdgeAgentEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var groupId = $scope.formValues.GroupId;
|
||||
var tags = $scope.formValues.Tags;
|
||||
var URL = window.location.hostname;
|
||||
|
||||
addEndpoint(name, 4, URL, "", groupId, tags, false, false, false, null, null, null);
|
||||
};
|
||||
|
||||
$scope.addAzureEndpoint = function() {
|
||||
var name = $scope.formValues.Name;
|
||||
var applicationId = $scope.formValues.AzureApplicationId;
|
||||
@@ -85,9 +94,13 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService,
|
||||
function addEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||
$scope.state.actionInProgress = true;
|
||||
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||
.then(function success() {
|
||||
.then(function success(data) {
|
||||
Notifications.success('Endpoint created', name);
|
||||
$state.go('portainer.endpoints', {}, {reload: true});
|
||||
if (type === 4) {
|
||||
$state.go('portainer.endpoints.endpoint', { id: data.Id });
|
||||
} else {
|
||||
$state.go('portainer.endpoints', {}, {reload: true});
|
||||
}
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to create endpoint');
|
||||
|
||||
@@ -36,6 +36,16 @@
|
||||
<p>Portainer agent</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="edge_agent_endpoint" ng-model="state.EnvironmentType" value="edge_agent">
|
||||
<label for="edge_agent_endpoint">
|
||||
<div class="boxselector_header">
|
||||
<i class="fa fa-cloud" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Edge Agent
|
||||
</div>
|
||||
<p>Portainer Edge agent</p>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="azure_endpoint" ng-model="state.EnvironmentType" value="azure">
|
||||
<label for="azure_endpoint">
|
||||
@@ -77,6 +87,16 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="state.EnvironmentType === 'edge_agent'">
|
||||
<div class="col-sm-12 form-section-title" >
|
||||
Information
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
Allows you to create an endpoint that can be registered with an Edge agent. All the required information on how to connect an Edge agent to this endpoint will be available after endpoint creation.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="state.EnvironmentType === 'azure'">
|
||||
<div class="col-sm-12 form-section-title" >
|
||||
Information
|
||||
@@ -238,6 +258,10 @@
|
||||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||
</button>
|
||||
<button ng-if="state.EnvironmentType === 'edge_agent'" type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !endpointCreationForm.$valid" ng-click="addEdgeAgentEndpoint()" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||
</button>
|
||||
<button ng-if="state.EnvironmentType === 'azure'" type="submit" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !endpointCreationForm.$valid" ng-click="addAzureEndpoint()" button-spinner="state.actionInProgress">
|
||||
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</span>
|
||||
<span ng-show="state.actionInProgress">Creating endpoint...</span>
|
||||
|
||||
@@ -5,6 +5,80 @@
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<information-panel ng-if="endpoint.Type === 4" title-text="Deploy an agent">
|
||||
<span class="small text-muted">
|
||||
<p>
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Deploy the Edge agent on your remote Docker environment using the following command
|
||||
</p>
|
||||
<div style="margin-top: 10px;">
|
||||
<uib-tabset active="state.deploymentTab">
|
||||
<uib-tab index="0" heading="Standalone">
|
||||
<code style=display:block;white-space:pre-wrap>
|
||||
docker run -d -v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /var/lib/docker/volumes:/var/lib/docker/volumes \
|
||||
-v /:/host \
|
||||
--restart always \
|
||||
-e EDGE=1 \
|
||||
-e CAP_HOST_MANAGEMENT=1 \
|
||||
-p 80:80 \
|
||||
--name portainer_edge_agent \
|
||||
portainer/pagent:edge
|
||||
</code>
|
||||
</uib-tab>
|
||||
<uib-tab index="1" heading="Swarm">
|
||||
<code style=display:block;white-space:pre-wrap>
|
||||
docker network create \
|
||||
--driver overlay \
|
||||
--attachable \
|
||||
portainer_agent_network;
|
||||
|
||||
docker service create \
|
||||
--name portainer_edge_agent \
|
||||
--network portainer_agent_network \
|
||||
-e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent \
|
||||
-e EDGE=1 \
|
||||
-e CAP_HOST_MANAGEMENT=1 \
|
||||
--mode global \
|
||||
--publish mode=host,target=80,published=80 \
|
||||
--constraint 'node.platform.os == linux' \
|
||||
--mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \
|
||||
--mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volumes \
|
||||
--mount type=bind,src=//,dst=/host \
|
||||
portainer/pagent:edge
|
||||
</code>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
<div style="margin-top: 10px;">
|
||||
<span class="btn btn-primary btn-sm" ng-click="copyEdgeAgentDeploymentCommand()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy command</span>
|
||||
<span id="copyNotificationDeploymentCommand" style="margin-left: 7px; display: none; color: #23ae89;">
|
||||
<i class="fa fa-check" aria-hidden="true" ></i> copied
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title" style="margin-top: 25px;">
|
||||
Join token
|
||||
</div>
|
||||
<p>
|
||||
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
Use the following join token to associate the Edge agent with this endpoint
|
||||
</p>
|
||||
<div style="margin-top: 10px;">
|
||||
<code style=display:block;white-space:pre-wrap>
|
||||
{{ endpoint.EdgeKey }}
|
||||
</code>
|
||||
<div style="margin-top: 10px;">
|
||||
<span class="btn btn-primary btn-sm" ng-click="copyEdgeAgentKey()"><i class="fa fa-copy space-right" aria-hidden="true"></i>Copy token</span>
|
||||
<span id="copyNotificationEdgeKey" style="margin-left: 7px; display: none; color: #23ae89;">
|
||||
<i class="fa fa-check" aria-hidden="true" ></i> copied
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</information-panel>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
@@ -22,7 +96,7 @@
|
||||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- endpoint-url-input -->
|
||||
<div class="form-group">
|
||||
<div class="form-group" ng-if="endpoint.Type !== 4">
|
||||
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Endpoint URL
|
||||
<portainer-tooltip position="bottom" message="URL or IP address of a Docker host. The Docker API must be exposed over a TCP port. Please refer to the Docker documentation to configure it."></portainer-tooltip>
|
||||
@@ -70,7 +144,7 @@
|
||||
</div>
|
||||
<!-- !tags -->
|
||||
<!-- endpoint-security -->
|
||||
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3">
|
||||
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3 && endpoint.Type !== 4">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Security
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EndpointSecurityFormData } from '../../../components/endpointSecurity/porEndpointSecurityModel';
|
||||
|
||||
angular.module('portainer.app')
|
||||
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications',
|
||||
function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupService, TagService, EndpointProvider, Notifications) {
|
||||
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications',
|
||||
function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, GroupService, TagService, EndpointProvider, Notifications) {
|
||||
|
||||
if (!$scope.applicationState.application.endpointManagement) {
|
||||
$state.go('portainer.endpoints');
|
||||
@@ -10,13 +10,28 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi
|
||||
|
||||
$scope.state = {
|
||||
uploadInProgress: false,
|
||||
actionInProgress: false
|
||||
actionInProgress: false,
|
||||
deploymentTab: 0
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
SecurityFormData: new EndpointSecurityFormData()
|
||||
};
|
||||
|
||||
$scope.copyEdgeAgentDeploymentCommand = function() {
|
||||
if ($scope.state.deploymentTab === 0) {
|
||||
clipboard.copyText('docker run -d -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes -v /:/host --restart always -e EDGE=1 -e CAP_HOST_MANAGEMENT=1 --name portainer_agent_iot portainer/pagent:edge');
|
||||
} else {
|
||||
clipboard.copyText('docker network create --driver overlay --attachable portainer_agent_network; docker service create --name portainer_edge_agent --network portainer_agent_network -e AGENT_CLUSTER_ADDR=tasks.portainer_edge_agent -e EDGE=1 -e CAP_HOST_MANAGEMENT=1 --mode global --publish mode=host,target=80,published=80 --constraint \'node.platform.os == linux\' --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock --mount type=bind,src=//var/lib/docker/volumes,dst=/var/lib/docker/volume --mount type=bind,src=//,dst=/host portainer/pagent:edge');
|
||||
}
|
||||
$('#copyNotificationDeploymentCommand').show().fadeOut(2500);
|
||||
};
|
||||
|
||||
$scope.copyEdgeAgentKey = function() {
|
||||
clipboard.copyText($scope.endpoint.EdgeKey);
|
||||
$('#copyNotificationEdgeKey').show().fadeOut(2500);
|
||||
};
|
||||
|
||||
$scope.updateEndpoint = function() {
|
||||
var endpoint = $scope.endpoint;
|
||||
var securityData = $scope.formValues.SecurityFormData;
|
||||
|
||||
@@ -48,7 +48,7 @@ module.exports = function(grunt) {
|
||||
]);
|
||||
});
|
||||
|
||||
grunt.task.registerTask('devopsbuild', 'devopsbuild:<platform>:<arch>',
|
||||
grunt.task.registerTask('devopsbuild', 'devopsbuild:<platform>:<arch>',
|
||||
function(p, a) {
|
||||
grunt.task.run([
|
||||
'config:prod',
|
||||
@@ -146,7 +146,7 @@ gruntfile_cfg.copy = {
|
||||
files: [
|
||||
{
|
||||
dest: '<%= root %>/',
|
||||
src: 'templates.json',
|
||||
src: 'templates.json',
|
||||
cwd: ''
|
||||
}
|
||||
]
|
||||
@@ -185,7 +185,7 @@ function shell_buildBinaryOnDevOps(p, a) {
|
||||
function shell_run() {
|
||||
return [
|
||||
'docker rm -f portainer',
|
||||
'docker run -d -p 9000:9000 -v $(pwd)/dist:/app -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer --no-analytics --template-file /app/templates.json'
|
||||
'docker run -d -p 9999:9999 -p 9000:9000 -v $(pwd)/dist:/app -v /tmp/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock:z --name portainer portainer/base /app/portainer --no-analytics --template-file /app/templates.json'
|
||||
].join(';');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user