mirror of
https://github.com/portainer/portainer.git
synced 2025-08-03 04:45:21 +02:00
feat(azure): add experimental Azure endpoint support (#1936)
This commit is contained in:
parent
415c6ce5e1
commit
9ad9cc5e2d
52 changed files with 1665 additions and 79 deletions
|
@ -44,6 +44,11 @@ const (
|
|||
ErrEndpointAccessDenied = Error("Access denied to endpoint")
|
||||
)
|
||||
|
||||
// Azure environment errors
|
||||
const (
|
||||
ErrAzureInvalidCredentials = Error("Invalid Azure credentials")
|
||||
)
|
||||
|
||||
// Endpoint group errors.
|
||||
const (
|
||||
ErrEndpointGroupNotFound = Error("Endpoint group not found")
|
||||
|
|
|
@ -2,15 +2,68 @@ package client
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
)
|
||||
|
||||
// HTTPClient represents a client to send HTTP requests.
|
||||
type HTTPClient struct {
|
||||
*http.Client
|
||||
}
|
||||
|
||||
// NewHTTPClient is used to build a new HTTPClient.
|
||||
func NewHTTPClient() *HTTPClient {
|
||||
return &HTTPClient{
|
||||
&http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AzureAuthenticationResponse represents an Azure API authentication response.
|
||||
type AzureAuthenticationResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresOn string `json:"expires_on"`
|
||||
}
|
||||
|
||||
// ExecuteAzureAuthenticationRequest is used to execute an authentication request
|
||||
// against the Azure API. It re-uses the same http.Client.
|
||||
func (client *HTTPClient) ExecuteAzureAuthenticationRequest(credentials *portainer.AzureCredentials) (*AzureAuthenticationResponse, error) {
|
||||
loginURL := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/token", credentials.TenantID)
|
||||
params := url.Values{
|
||||
"grant_type": {"client_credentials"},
|
||||
"client_id": {credentials.ApplicationID},
|
||||
"client_secret": {credentials.AuthenticationKey},
|
||||
"resource": {"https://management.azure.com/"},
|
||||
}
|
||||
|
||||
response, err := client.PostForm(loginURL, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, portainer.ErrAzureInvalidCredentials
|
||||
}
|
||||
|
||||
var token AzureAuthenticationResponse
|
||||
err = json.NewDecoder(response.Body).Decode(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// ExecutePingOperation will send a SystemPing operation HTTP request to a Docker environment
|
||||
// using the specified host and optional TLS configuration.
|
||||
// It uses a new Http.Client for each operation.
|
||||
func ExecutePingOperation(host string, tlsConfig *tls.Config) (bool, error) {
|
||||
transport := &http.Transport{}
|
||||
|
||||
|
|
102
api/http/handler/azure.go
Normal file
102
api/http/handler/azure.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
httperror "github.com/portainer/portainer/http/error"
|
||||
"github.com/portainer/portainer/http/proxy"
|
||||
"github.com/portainer/portainer/http/security"
|
||||
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// AzureHandler represents an HTTP API handler for proxying requests to the Azure API.
|
||||
type AzureHandler struct {
|
||||
*mux.Router
|
||||
Logger *log.Logger
|
||||
EndpointService portainer.EndpointService
|
||||
EndpointGroupService portainer.EndpointGroupService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
ProxyManager *proxy.Manager
|
||||
}
|
||||
|
||||
// NewAzureHandler returns a new instance of AzureHandler.
|
||||
func NewAzureHandler(bouncer *security.RequestBouncer) *AzureHandler {
|
||||
h := &AzureHandler{
|
||||
Router: mux.NewRouter(),
|
||||
Logger: log.New(os.Stderr, "", log.LstdFlags),
|
||||
}
|
||||
h.PathPrefix("/{id}/azure").Handler(
|
||||
bouncer.AuthenticatedAccess(http.HandlerFunc(h.proxyRequestsToAzureAPI)))
|
||||
return h
|
||||
}
|
||||
|
||||
func (handler *AzureHandler) checkEndpointAccess(endpoint *portainer.Endpoint, userID portainer.UserID) error {
|
||||
memberships, err := handler.TeamMembershipService.TeamMembershipsByUserID(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group, err := handler.EndpointGroupService.EndpointGroup(endpoint.GroupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !security.AuthorizedEndpointAccess(endpoint, group, userID, memberships) {
|
||||
return portainer.ErrEndpointAccessDenied
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *AzureHandler) proxyRequestsToAzureAPI(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
parsedID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusBadRequest, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
endpointID := portainer.EndpointID(parsedID)
|
||||
endpoint, err := handler.EndpointService.Endpoint(endpointID)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
tokenData, err := security.RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
|
||||
if tokenData.Role != portainer.AdministratorRole {
|
||||
err = handler.checkEndpointAccess(endpoint, tokenData.ID)
|
||||
if err != nil && err == portainer.ErrEndpointAccessDenied {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusForbidden, handler.Logger)
|
||||
return
|
||||
} else if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var proxy http.Handler
|
||||
proxy = handler.ProxyManager.GetProxy(string(endpointID))
|
||||
if proxy == nil {
|
||||
proxy, err = handler.ProxyManager.CreateAndRegisterProxy(endpoint)
|
||||
if err != nil {
|
||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.StripPrefix("/"+id+"/azure", proxy).ServeHTTP(w, r)
|
||||
}
|
|
@ -80,6 +80,7 @@ type (
|
|||
postEndpointPayload struct {
|
||||
name string
|
||||
url string
|
||||
endpointType int
|
||||
publicURL string
|
||||
groupID int
|
||||
useTLS bool
|
||||
|
@ -88,6 +89,9 @@ type (
|
|||
caCert []byte
|
||||
cert []byte
|
||||
key []byte
|
||||
azureApplicationID string
|
||||
azureTenantID string
|
||||
azureAuthenticationKey string
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -117,9 +121,46 @@ func (handler *EndpointHandler) handleGetEndpoints(w http.ResponseWriter, r *htt
|
|||
return
|
||||
}
|
||||
|
||||
for i := range filteredEndpoints {
|
||||
filteredEndpoints[i].AzureCredentials = portainer.AzureCredentials{}
|
||||
}
|
||||
|
||||
encodeJSON(w, filteredEndpoints, handler.Logger)
|
||||
}
|
||||
|
||||
func (handler *EndpointHandler) createAzureEndpoint(payload *postEndpointPayload) (*portainer.Endpoint, error) {
|
||||
credentials := portainer.AzureCredentials{
|
||||
ApplicationID: payload.azureApplicationID,
|
||||
TenantID: payload.azureTenantID,
|
||||
AuthenticationKey: payload.azureAuthenticationKey,
|
||||
}
|
||||
|
||||
httpClient := client.NewHTTPClient()
|
||||
_, err := httpClient.ExecuteAzureAuthenticationRequest(&credentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoint := &portainer.Endpoint{
|
||||
Name: payload.name,
|
||||
URL: payload.url,
|
||||
Type: portainer.AzureEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.groupID),
|
||||
PublicURL: payload.publicURL,
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
AzureCredentials: credentials,
|
||||
}
|
||||
|
||||
err = handler.EndpointService.CreateEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
func (handler *EndpointHandler) createTLSSecuredEndpoint(payload *postEndpointPayload) (*portainer.Endpoint, error) {
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromBytes(payload.caCert, payload.cert, payload.key, payload.skipTLSClientVerification, payload.skipTLSServerVerification)
|
||||
if err != nil {
|
||||
|
@ -236,6 +277,10 @@ func (handler *EndpointHandler) createUnsecuredEndpoint(payload *postEndpointPay
|
|||
}
|
||||
|
||||
func (handler *EndpointHandler) createEndpoint(payload *postEndpointPayload) (*portainer.Endpoint, error) {
|
||||
if portainer.EndpointType(payload.endpointType) == portainer.AzureEnvironment {
|
||||
return handler.createAzureEndpoint(payload)
|
||||
}
|
||||
|
||||
if payload.useTLS {
|
||||
return handler.createTLSSecuredEndpoint(payload)
|
||||
}
|
||||
|
@ -245,11 +290,35 @@ func (handler *EndpointHandler) createEndpoint(payload *postEndpointPayload) (*p
|
|||
func convertPostEndpointRequestToPayload(r *http.Request) (*postEndpointPayload, error) {
|
||||
payload := &postEndpointPayload{}
|
||||
payload.name = r.FormValue("Name")
|
||||
|
||||
endpointType := r.FormValue("EndpointType")
|
||||
|
||||
if payload.name == "" || endpointType == "" {
|
||||
return nil, ErrInvalidRequestFormat
|
||||
}
|
||||
|
||||
parsedType, err := strconv.Atoi(endpointType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload.url = r.FormValue("URL")
|
||||
payload.endpointType = parsedType
|
||||
|
||||
if portainer.EndpointType(payload.endpointType) != portainer.AzureEnvironment && payload.url == "" {
|
||||
return nil, ErrInvalidRequestFormat
|
||||
}
|
||||
|
||||
payload.publicURL = r.FormValue("PublicURL")
|
||||
|
||||
if payload.name == "" || payload.url == "" {
|
||||
return nil, ErrInvalidRequestFormat
|
||||
if portainer.EndpointType(payload.endpointType) == portainer.AzureEnvironment {
|
||||
payload.azureApplicationID = r.FormValue("AzureApplicationID")
|
||||
payload.azureTenantID = r.FormValue("AzureTenantID")
|
||||
payload.azureAuthenticationKey = r.FormValue("AzureAuthenticationKey")
|
||||
|
||||
if payload.azureApplicationID == "" || payload.azureTenantID == "" || payload.azureAuthenticationKey == "" {
|
||||
return nil, ErrInvalidRequestFormat
|
||||
}
|
||||
}
|
||||
|
||||
rawGroupID := r.FormValue("GroupID")
|
||||
|
@ -336,6 +405,8 @@ func (handler *EndpointHandler) handleGetEndpoint(w http.ResponseWriter, r *http
|
|||
return
|
||||
}
|
||||
|
||||
endpoint.AzureCredentials = portainer.AzureCredentials{}
|
||||
|
||||
encodeJSON(w, endpoint, handler.Logger)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ type Handler struct {
|
|||
SettingsHandler *SettingsHandler
|
||||
TemplatesHandler *TemplatesHandler
|
||||
DockerHandler *DockerHandler
|
||||
AzureHandler *AzureHandler
|
||||
WebSocketHandler *WebSocketHandler
|
||||
UploadHandler *UploadHandler
|
||||
FileHandler *FileHandler
|
||||
|
@ -64,6 +65,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.StripPrefix("/api/endpoints", h.StoridgeHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/extensions"):
|
||||
http.StripPrefix("/api/endpoints", h.ExtensionHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/azure/"):
|
||||
http.StripPrefix("/api/endpoints", h.AzureHandler).ServeHTTP(w, r)
|
||||
default:
|
||||
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
|
||||
}
|
||||
|
|
81
api/http/proxy/azure_transport.go
Normal file
81
api/http/proxy/azure_transport.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer"
|
||||
"github.com/portainer/portainer/http/client"
|
||||
)
|
||||
|
||||
type (
|
||||
azureAPIToken struct {
|
||||
value string
|
||||
expirationTime time.Time
|
||||
}
|
||||
|
||||
// AzureTransport represents a transport used when executing HTTP requests
|
||||
// against the Azure API.
|
||||
AzureTransport struct {
|
||||
credentials *portainer.AzureCredentials
|
||||
client *client.HTTPClient
|
||||
token *azureAPIToken
|
||||
mutex sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
// NewAzureTransport returns a pointer to an AzureTransport instance.
|
||||
func NewAzureTransport(credentials *portainer.AzureCredentials) *AzureTransport {
|
||||
return &AzureTransport{
|
||||
credentials: credentials,
|
||||
client: client.NewHTTPClient(),
|
||||
}
|
||||
}
|
||||
|
||||
func (transport *AzureTransport) authenticate() error {
|
||||
token, err := transport.client.ExecuteAzureAuthenticationRequest(transport.credentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
expiresOn, err := strconv.ParseInt(token.ExpiresOn, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
transport.token = &azureAPIToken{
|
||||
value: token.AccessToken,
|
||||
expirationTime: time.Unix(expiresOn, 0),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (transport *AzureTransport) retrieveAuthenticationToken() error {
|
||||
transport.mutex.Lock()
|
||||
defer transport.mutex.Unlock()
|
||||
|
||||
if transport.token == nil {
|
||||
return transport.authenticate()
|
||||
}
|
||||
|
||||
timeLimit := time.Now().Add(-5 * time.Minute)
|
||||
if timeLimit.After(transport.token.expirationTime) {
|
||||
return transport.authenticate()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoundTrip is the implementation of the Transport interface.
|
||||
func (transport *AzureTransport) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
err := transport.retrieveAuthenticationToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Header.Set("Authorization", "Bearer "+transport.token.value)
|
||||
return http.DefaultTransport.RoundTrip(request)
|
||||
}
|
|
@ -10,6 +10,8 @@ import (
|
|||
"github.com/portainer/portainer/crypto"
|
||||
)
|
||||
|
||||
const azureAPIBaseURL = "https://management.azure.com"
|
||||
|
||||
// proxyFactory is a factory to create reverse proxies to Docker endpoints
|
||||
type proxyFactory struct {
|
||||
ResourceControlService portainer.ResourceControlService
|
||||
|
@ -20,11 +22,23 @@ type proxyFactory struct {
|
|||
SignatureService portainer.DigitalSignatureService
|
||||
}
|
||||
|
||||
func (factory *proxyFactory) newExtensionHTTPPRoxy(u *url.URL) http.Handler {
|
||||
func (factory *proxyFactory) newHTTPProxy(u *url.URL) http.Handler {
|
||||
u.Scheme = "http"
|
||||
return newSingleHostReverseProxyWithHostHeader(u)
|
||||
}
|
||||
|
||||
func newAzureProxy(credentials *portainer.AzureCredentials) (http.Handler, error) {
|
||||
url, err := url.Parse(azureAPIBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy := newSingleHostReverseProxyWithHostHeader(url)
|
||||
proxy.Transport = NewAzureTransport(credentials)
|
||||
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
func (factory *proxyFactory) newDockerHTTPSProxy(u *url.URL, tlsConfig *portainer.TLSConfiguration, enableSignature bool) (http.Handler, error) {
|
||||
u.Scheme = "https"
|
||||
|
||||
|
|
|
@ -44,33 +44,39 @@ func NewManager(parameters *ManagerParams) *Manager {
|
|||
}
|
||||
}
|
||||
|
||||
// CreateAndRegisterProxy creates a new HTTP reverse proxy and adds it to the registered proxies.
|
||||
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
||||
func (manager *Manager) CreateAndRegisterProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
var proxy http.Handler
|
||||
func (manager *Manager) createDockerProxy(endpointURL *url.URL, tlsConfig *portainer.TLSConfiguration) (http.Handler, error) {
|
||||
if endpointURL.Scheme == "tcp" {
|
||||
if tlsConfig.TLS || tlsConfig.TLSSkipVerify {
|
||||
return manager.proxyFactory.newDockerHTTPSProxy(endpointURL, tlsConfig, false)
|
||||
}
|
||||
return manager.proxyFactory.newDockerHTTPProxy(endpointURL, false), nil
|
||||
}
|
||||
// Assume unix:// scheme
|
||||
return manager.proxyFactory.newDockerSocketProxy(endpointURL.Path), nil
|
||||
}
|
||||
|
||||
func (manager *Manager) createProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
endpointURL, err := url.Parse(endpoint.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enableSignature := false
|
||||
if endpoint.Type == portainer.AgentOnDockerEnvironment {
|
||||
enableSignature = true
|
||||
switch endpoint.Type {
|
||||
case portainer.AgentOnDockerEnvironment:
|
||||
return manager.proxyFactory.newDockerHTTPSProxy(endpointURL, &endpoint.TLSConfig, true)
|
||||
case portainer.AzureEnvironment:
|
||||
return newAzureProxy(&endpoint.AzureCredentials)
|
||||
default:
|
||||
return manager.createDockerProxy(endpointURL, &endpoint.TLSConfig)
|
||||
}
|
||||
}
|
||||
|
||||
if endpointURL.Scheme == "tcp" {
|
||||
if endpoint.TLSConfig.TLS {
|
||||
proxy, err = manager.proxyFactory.newDockerHTTPSProxy(endpointURL, &endpoint.TLSConfig, enableSignature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
proxy = manager.proxyFactory.newDockerHTTPProxy(endpointURL, enableSignature)
|
||||
}
|
||||
} else {
|
||||
// Assume unix:// scheme
|
||||
proxy = manager.proxyFactory.newDockerSocketProxy(endpointURL.Path)
|
||||
// CreateAndRegisterProxy creates a new HTTP reverse proxy based on endpoint properties and and adds it to the registered proxies.
|
||||
// It can also be used to create a new HTTP reverse proxy and replace an already registered proxy.
|
||||
func (manager *Manager) CreateAndRegisterProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
proxy, err := manager.createProxy(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
manager.proxies.Set(string(endpoint.ID), proxy)
|
||||
|
@ -99,7 +105,7 @@ func (manager *Manager) CreateAndRegisterExtensionProxy(key, extensionAPIURL str
|
|||
return nil, err
|
||||
}
|
||||
|
||||
proxy := manager.proxyFactory.newExtensionHTTPPRoxy(extensionURL)
|
||||
proxy := manager.proxyFactory.newHTTPProxy(extensionURL)
|
||||
manager.extensionProxies.Set(key, proxy)
|
||||
return proxy, nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// NewSingleHostReverseProxyWithHostHeader is based on NewSingleHostReverseProxy
|
||||
// newSingleHostReverseProxyWithHostHeader is based on NewSingleHostReverseProxy
|
||||
// from golang.org/src/net/http/httputil/reverseproxy.go and merely sets the Host
|
||||
// HTTP header, which NewSingleHostReverseProxy deliberately preserves.
|
||||
func newSingleHostReverseProxyWithHostHeader(target *url.URL) *httputil.ReverseProxy {
|
||||
|
|
|
@ -88,6 +88,11 @@ func (server *Server) Start() error {
|
|||
dockerHandler.EndpointGroupService = server.EndpointGroupService
|
||||
dockerHandler.TeamMembershipService = server.TeamMembershipService
|
||||
dockerHandler.ProxyManager = proxyManager
|
||||
var azureHandler = handler.NewAzureHandler(requestBouncer)
|
||||
azureHandler.EndpointService = server.EndpointService
|
||||
azureHandler.EndpointGroupService = server.EndpointGroupService
|
||||
azureHandler.TeamMembershipService = server.TeamMembershipService
|
||||
azureHandler.ProxyManager = proxyManager
|
||||
var websocketHandler = handler.NewWebSocketHandler()
|
||||
websocketHandler.EndpointService = server.EndpointService
|
||||
websocketHandler.SignatureService = server.SignatureService
|
||||
|
@ -140,6 +145,7 @@ func (server *Server) Start() error {
|
|||
StackHandler: stackHandler,
|
||||
TemplatesHandler: templatesHandler,
|
||||
DockerHandler: dockerHandler,
|
||||
AzureHandler: azureHandler,
|
||||
WebSocketHandler: websocketHandler,
|
||||
FileHandler: fileHandler,
|
||||
UploadHandler: uploadHandler,
|
||||
|
|
|
@ -175,16 +175,17 @@ type (
|
|||
// Endpoint represents a Docker endpoint with all the info required
|
||||
// to connect to it.
|
||||
Endpoint struct {
|
||||
ID EndpointID `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Type EndpointType `json:"Type"`
|
||||
URL string `json:"URL"`
|
||||
GroupID EndpointGroupID `json:"GroupId"`
|
||||
PublicURL string `json:"PublicURL"`
|
||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
||||
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
||||
Extensions []EndpointExtension `json:"Extensions"`
|
||||
ID EndpointID `json:"Id"`
|
||||
Name string `json:"Name"`
|
||||
Type EndpointType `json:"Type"`
|
||||
URL string `json:"URL"`
|
||||
GroupID EndpointGroupID `json:"GroupId"`
|
||||
PublicURL string `json:"PublicURL"`
|
||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
||||
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
||||
Extensions []EndpointExtension `json:"Extensions"`
|
||||
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
|
||||
|
||||
// Deprecated fields
|
||||
// Deprecated in DBVersion == 4
|
||||
|
@ -194,6 +195,14 @@ type (
|
|||
TLSKeyPath string `json:"TLSKey,omitempty"`
|
||||
}
|
||||
|
||||
// AzureCredentials represents the credentials used to connect to an Azure
|
||||
// environment.
|
||||
AzureCredentials struct {
|
||||
ApplicationID string `json:"ApplicationID"`
|
||||
TenantID string `json:"TenantID"`
|
||||
AuthenticationKey string `json:"AuthenticationKey"`
|
||||
}
|
||||
|
||||
// EndpointGroupID represents an endpoint group identifier.
|
||||
EndpointGroupID int
|
||||
|
||||
|
@ -530,4 +539,6 @@ const (
|
|||
DockerEnvironment
|
||||
// AgentOnDockerEnvironment represents an endpoint connected to a Portainer agent deployed on a Docker environment
|
||||
AgentOnDockerEnvironment
|
||||
// AzureEnvironment represents an endpoint connected to an Azure environment
|
||||
AzureEnvironment
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue