mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
revert(azure): revert removal (#3890)
* Revert "fix(sidebar): show docker sidebar when needed (#3852)" This reverts commit59da17dde4
. * Revert "refactor(azure): remove Azure ACI endpoint support (#3803)" This reverts commit493de20540
.
This commit is contained in:
parent
25ca036070
commit
b58c2facfe
65 changed files with 1793 additions and 50 deletions
|
@ -53,7 +53,7 @@ func (runner *SnapshotJobRunner) Run() {
|
|||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.Type == portainer.EdgeAgentEnvironment {
|
||||
if endpoint.Type == portainer.AzureEnvironment || endpoint.Type == portainer.EdgeAgentEnvironment {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,9 @@ func NewClientFactory(signatureService portainer.DigitalSignatureService, revers
|
|||
// a specific endpoint configuration. The nodeName parameter can be used
|
||||
// with an agent enabled endpoint to target a specific node in an agent cluster.
|
||||
func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint, nodeName string) (*client.Client, error) {
|
||||
if endpoint.Type == portainer.AgentOnDockerEnvironment {
|
||||
if endpoint.Type == portainer.AzureEnvironment {
|
||||
return nil, unsupportedEnvironmentType
|
||||
} else if endpoint.Type == portainer.AgentOnDockerEnvironment {
|
||||
return createAgentClient(endpoint, factory.signatureService, nodeName)
|
||||
} else if endpoint.Type == portainer.EdgeAgentEnvironment {
|
||||
return createEdgeClient(endpoint, factory.reverseTunnelService, nodeName)
|
||||
|
|
|
@ -39,6 +39,11 @@ const (
|
|||
ErrEndpointAccessDenied = Error("Access denied to endpoint")
|
||||
)
|
||||
|
||||
// Azure environment errors
|
||||
const (
|
||||
ErrAzureInvalidCredentials = Error("Invalid Azure credentials")
|
||||
)
|
||||
|
||||
// Endpoint group errors.
|
||||
const (
|
||||
ErrCannotRemoveDefaultGroup = Error("Cannot remove the default endpoint group")
|
||||
|
|
|
@ -171,7 +171,6 @@ github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2 h1:0PfgGLys9yH
|
|||
github.com/portainer/libcrypto v0.0.0-20190723020515-23ebe86ab2c2/go.mod h1:/wIeGwJOMYc1JplE/OvYMO5korce39HddIfI8VKGyAM=
|
||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33 h1:H8HR2dHdBf8HANSkUyVw4o8+4tegGcd+zyKZ3e599II=
|
||||
github.com/portainer/libhttp v0.0.0-20190806161843-ba068f58be33/go.mod h1:Y2TfgviWI4rT2qaOTHr+hq6MdKIE5YjgQAu7qwptTV0=
|
||||
github.com/portainer/portainer v0.10.1 h1:I8K345CjGWfUGsVA8c8/gqamwLCC6CIAjxZXSklAFq0=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
|
||||
|
|
|
@ -2,9 +2,12 @@ package client
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -16,6 +19,55 @@ const (
|
|||
defaultHTTPTimeout = 5
|
||||
)
|
||||
|
||||
// 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 * time.Duration(defaultHTTPTimeout),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Get executes a simple HTTP GET to the specified URL and returns
|
||||
// the content of the response body. Timeout can be specified via the timeout parameter,
|
||||
// will default to defaultHTTPTimeout if set to 0.
|
||||
|
|
|
@ -23,6 +23,8 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
requestBouncer: bouncer,
|
||||
}
|
||||
h.PathPrefix("/{id}/azure").Handler(
|
||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToAzureAPI)))
|
||||
h.PathPrefix("/{id}/docker").Handler(
|
||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToDockerAPI)))
|
||||
h.PathPrefix("/{id}/storidge").Handler(
|
||||
|
|
43
api/http/handler/endpointproxy/proxy_azure.go
Normal file
43
api/http/handler/endpointproxy/proxy_azure.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package endpointproxy
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/portainer/api"
|
||||
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (handler *Handler) proxyRequestsToAzureAPI(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
||||
}
|
||||
|
||||
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
var proxy http.Handler
|
||||
proxy = handler.ProxyManager.GetEndpointProxy(endpoint)
|
||||
if proxy == nil {
|
||||
proxy, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create proxy", err}
|
||||
}
|
||||
}
|
||||
|
||||
id := strconv.Itoa(endpointID)
|
||||
http.StripPrefix("/"+id+"/azure", proxy).ServeHTTP(w, r)
|
||||
return nil
|
||||
}
|
|
@ -18,19 +18,22 @@ import (
|
|||
)
|
||||
|
||||
type endpointCreatePayload struct {
|
||||
Name string
|
||||
URL string
|
||||
EndpointType int
|
||||
PublicURL string
|
||||
GroupID int
|
||||
TLS bool
|
||||
TLSSkipVerify bool
|
||||
TLSSkipClientVerify bool
|
||||
TLSCACertFile []byte
|
||||
TLSCertFile []byte
|
||||
TLSKeyFile []byte
|
||||
TagIDs []portainer.TagID
|
||||
EdgeCheckinInterval int
|
||||
Name string
|
||||
URL string
|
||||
EndpointType int
|
||||
PublicURL string
|
||||
GroupID int
|
||||
TLS bool
|
||||
TLSSkipVerify bool
|
||||
TLSSkipClientVerify bool
|
||||
TLSCACertFile []byte
|
||||
TLSCertFile []byte
|
||||
TLSKeyFile []byte
|
||||
AzureApplicationID string
|
||||
AzureTenantID string
|
||||
AzureAuthenticationKey string
|
||||
TagIDs []portainer.TagID
|
||||
EdgeCheckinInterval int
|
||||
}
|
||||
|
||||
func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||
|
@ -42,7 +45,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 4 (Edge Agent 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
|
||||
|
||||
|
@ -94,14 +97,35 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
|||
}
|
||||
}
|
||||
|
||||
endpointURL, err := request.RetrieveMultiPartFormValue(r, "URL", true)
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid endpoint URL")
|
||||
}
|
||||
payload.URL = endpointURL
|
||||
switch portainer.EndpointType(payload.EndpointType) {
|
||||
case portainer.AzureEnvironment:
|
||||
azureApplicationID, err := request.RetrieveMultiPartFormValue(r, "AzureApplicationID", false)
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid Azure application ID")
|
||||
}
|
||||
payload.AzureApplicationID = azureApplicationID
|
||||
|
||||
publicURL, _ := request.RetrieveMultiPartFormValue(r, "PublicURL", true)
|
||||
payload.PublicURL = publicURL
|
||||
azureTenantID, err := request.RetrieveMultiPartFormValue(r, "AzureTenantID", false)
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid Azure tenant ID")
|
||||
}
|
||||
payload.AzureTenantID = azureTenantID
|
||||
|
||||
azureAuthenticationKey, err := request.RetrieveMultiPartFormValue(r, "AzureAuthenticationKey", false)
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid Azure authentication key")
|
||||
}
|
||||
payload.AzureAuthenticationKey = azureAuthenticationKey
|
||||
default:
|
||||
url, err := request.RetrieveMultiPartFormValue(r, "URL", true)
|
||||
if err != nil {
|
||||
return portainer.Error("Invalid endpoint URL")
|
||||
}
|
||||
payload.URL = url
|
||||
|
||||
publicURL, _ := request.RetrieveMultiPartFormValue(r, "PublicURL", true)
|
||||
payload.PublicURL = publicURL
|
||||
}
|
||||
|
||||
checkinInterval, _ := request.RetrieveNumericMultiPartFormValue(r, "CheckinInterval", true)
|
||||
payload.EdgeCheckinInterval = checkinInterval
|
||||
|
@ -158,7 +182,9 @@ 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.EdgeAgentEnvironment {
|
||||
if portainer.EndpointType(payload.EndpointType) == portainer.AzureEnvironment {
|
||||
return handler.createAzureEndpoint(payload)
|
||||
} else if portainer.EndpointType(payload.EndpointType) == portainer.EdgeAgentEnvironment {
|
||||
return handler.createEdgeAgentEndpoint(payload)
|
||||
}
|
||||
|
||||
|
@ -168,6 +194,44 @@ func (handler *Handler) createEndpoint(payload *endpointCreatePayload) (*portain
|
|||
return handler.createUnsecuredEndpoint(payload)
|
||||
}
|
||||
|
||||
func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||
credentials := portainer.AzureCredentials{
|
||||
ApplicationID: payload.AzureApplicationID,
|
||||
TenantID: payload.AzureTenantID,
|
||||
AuthenticationKey: payload.AzureAuthenticationKey,
|
||||
}
|
||||
|
||||
httpClient := client.NewHTTPClient()
|
||||
_, err := httpClient.ExecuteAzureAuthenticationRequest(&credentials)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to authenticate against Azure", err}
|
||||
}
|
||||
|
||||
endpointID := handler.DataStore.Endpoint().GetNextIdentifier()
|
||||
endpoint := &portainer.Endpoint{
|
||||
ID: portainer.EndpointID(endpointID),
|
||||
Name: payload.Name,
|
||||
URL: "https://management.azure.com",
|
||||
Type: portainer.AzureEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
PublicURL: payload.PublicURL,
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
AzureCredentials: credentials,
|
||||
TagIDs: payload.TagIDs,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
}
|
||||
|
||||
err = handler.saveEndpointAndUpdateAuthorizations(endpoint)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "An error occured while trying to create the endpoint", err}
|
||||
}
|
||||
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||
endpointType := portainer.EdgeAgentEnvironment
|
||||
endpointID := handler.DataStore.Endpoint().GetNextIdentifier()
|
||||
|
|
|
@ -23,6 +23,10 @@ func (handler *Handler) endpointSnapshot(w http.ResponseWriter, r *http.Request)
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
if endpoint.Type == portainer.AzureEnvironment {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Snapshots not supported for Azure endpoints", err}
|
||||
}
|
||||
|
||||
snapshot, snapshotError := handler.Snapshotter.CreateSnapshot(endpoint)
|
||||
|
||||
latestEndpointReference, err := handler.DataStore.Endpoint().Endpoint(endpoint.ID)
|
||||
|
|
|
@ -17,6 +17,10 @@ func (handler *Handler) endpointSnapshots(w http.ResponseWriter, r *http.Request
|
|||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.Type == portainer.AzureEnvironment {
|
||||
continue
|
||||
}
|
||||
|
||||
snapshot, snapshotError := handler.Snapshotter.CreateSnapshot(&endpoint)
|
||||
|
||||
latestEndpointReference, err := handler.DataStore.Endpoint().Endpoint(endpoint.ID)
|
||||
|
|
|
@ -9,21 +9,25 @@ import (
|
|||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/client"
|
||||
)
|
||||
|
||||
type endpointUpdatePayload struct {
|
||||
Name *string
|
||||
URL *string
|
||||
PublicURL *string
|
||||
GroupID *int
|
||||
TLS *bool
|
||||
TLSSkipVerify *bool
|
||||
TLSSkipClientVerify *bool
|
||||
Status *int
|
||||
TagIDs []portainer.TagID
|
||||
UserAccessPolicies portainer.UserAccessPolicies
|
||||
TeamAccessPolicies portainer.TeamAccessPolicies
|
||||
EdgeCheckinInterval *int
|
||||
Name *string
|
||||
URL *string
|
||||
PublicURL *string
|
||||
GroupID *int
|
||||
TLS *bool
|
||||
TLSSkipVerify *bool
|
||||
TLSSkipClientVerify *bool
|
||||
Status *int
|
||||
AzureApplicationID *string
|
||||
AzureTenantID *string
|
||||
AzureAuthenticationKey *string
|
||||
TagIDs []portainer.TagID
|
||||
UserAccessPolicies portainer.UserAccessPolicies
|
||||
TeamAccessPolicies portainer.TeamAccessPolicies
|
||||
EdgeCheckinInterval *int
|
||||
}
|
||||
|
||||
func (payload *endpointUpdatePayload) Validate(r *http.Request) error {
|
||||
|
@ -138,6 +142,26 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
|||
}
|
||||
}
|
||||
|
||||
if endpoint.Type == portainer.AzureEnvironment {
|
||||
credentials := endpoint.AzureCredentials
|
||||
if payload.AzureApplicationID != nil {
|
||||
credentials.ApplicationID = *payload.AzureApplicationID
|
||||
}
|
||||
if payload.AzureTenantID != nil {
|
||||
credentials.TenantID = *payload.AzureTenantID
|
||||
}
|
||||
if payload.AzureAuthenticationKey != nil {
|
||||
credentials.AuthenticationKey = *payload.AzureAuthenticationKey
|
||||
}
|
||||
|
||||
httpClient := client.NewHTTPClient()
|
||||
_, authErr := httpClient.ExecuteAzureAuthenticationRequest(&credentials)
|
||||
if authErr != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to authenticate against Azure", authErr}
|
||||
}
|
||||
endpoint.AzureCredentials = credentials
|
||||
}
|
||||
|
||||
if payload.TLS != nil {
|
||||
folder := strconv.Itoa(endpointID)
|
||||
|
||||
|
@ -182,7 +206,7 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
|||
}
|
||||
}
|
||||
|
||||
if payload.URL != nil || payload.TLS != nil {
|
||||
if payload.URL != nil || payload.TLS != nil || endpoint.Type == portainer.AzureEnvironment {
|
||||
_, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to register HTTP proxy for the endpoint", err}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
)
|
||||
|
||||
func hideFields(endpoint *portainer.Endpoint) {
|
||||
endpoint.AzureCredentials = portainer.AzureCredentials{}
|
||||
if len(endpoint.Snapshots) > 0 {
|
||||
endpoint.Snapshots[0].SnapshotRaw = portainer.SnapshotRaw{}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/storidge/"):
|
||||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/azure/"):
|
||||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/edge/"):
|
||||
http.StripPrefix("/api/endpoints", h.EndpointEdgeHandler).ServeHTTP(w, r)
|
||||
default:
|
||||
|
|
20
api/http/proxy/factory/azure.go
Normal file
20
api/http/proxy/factory/azure.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory/azure"
|
||||
)
|
||||
|
||||
func newAzureProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
remoteURL, err := url.Parse(azureAPIBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy := newSingleHostReverseProxyWithHostHeader(remoteURL)
|
||||
proxy.Transport = azure.NewTransport(&endpoint.AzureCredentials)
|
||||
return proxy, nil
|
||||
}
|
80
api/http/proxy/factory/azure/transport.go
Normal file
80
api/http/proxy/factory/azure/transport.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package azure
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/client"
|
||||
)
|
||||
|
||||
type (
|
||||
azureAPIToken struct {
|
||||
value string
|
||||
expirationTime time.Time
|
||||
}
|
||||
|
||||
Transport struct {
|
||||
credentials *portainer.AzureCredentials
|
||||
client *client.HTTPClient
|
||||
token *azureAPIToken
|
||||
mutex sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
// NewTransport returns a pointer to a new instance of Transport that implements the HTTP Transport
|
||||
// interface for proxying requests to the Azure API.
|
||||
func NewTransport(credentials *portainer.AzureCredentials) *Transport {
|
||||
return &Transport{
|
||||
credentials: credentials,
|
||||
client: client.NewHTTPClient(),
|
||||
}
|
||||
}
|
||||
|
||||
// RoundTrip is the implementation of the the http.RoundTripper interface
|
||||
func (transport *Transport) 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)
|
||||
}
|
||||
|
||||
func (transport *Transport) 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 *Transport) 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
|
||||
}
|
|
@ -10,6 +10,8 @@ import (
|
|||
"github.com/portainer/portainer/api/docker"
|
||||
)
|
||||
|
||||
const azureAPIBaseURL = "https://management.azure.com"
|
||||
|
||||
var extensionPorts = map[portainer.ExtensionID]string{
|
||||
portainer.RegistryManagementExtension: "7001",
|
||||
portainer.OAuthAuthenticationExtension: "7002",
|
||||
|
@ -69,6 +71,11 @@ func (factory *ProxyFactory) NewLegacyExtensionProxy(extensionAPIURL string) (ht
|
|||
|
||||
// NewEndpointProxy returns a new reverse proxy (filesystem based or HTTP) to an endpoint API server
|
||||
func (factory *ProxyFactory) NewEndpointProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
switch endpoint.Type {
|
||||
case portainer.AzureEnvironment:
|
||||
return newAzureProxy(endpoint)
|
||||
}
|
||||
|
||||
return factory.newDockerProxy(endpoint)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ type (
|
|||
Authorizations map[Authorization]bool
|
||||
|
||||
// AzureCredentials represents the credentials used to connect to an Azure
|
||||
// environment (deprecated).
|
||||
// environment.
|
||||
AzureCredentials struct {
|
||||
ApplicationID string `json:"ApplicationID"`
|
||||
TenantID string `json:"TenantID"`
|
||||
|
@ -163,6 +163,7 @@ type (
|
|||
PublicURL string `json:"PublicURL"`
|
||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||
Extensions []EndpointExtension `json:"Extensions"`
|
||||
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
|
||||
TagIDs []TagID `json:"TagIds"`
|
||||
Status EndpointStatus `json:"Status"`
|
||||
Snapshots []Snapshot `json:"Snapshots"`
|
||||
|
@ -185,9 +186,6 @@ type (
|
|||
|
||||
// Deprecated in DBVersion == 22
|
||||
Tags []string `json:"Tags"`
|
||||
|
||||
// Deprecated in DBVersion == 24
|
||||
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty"`
|
||||
}
|
||||
|
||||
// EndpointAuthorizations represents the authorizations associated to a set of endpoints
|
||||
|
@ -1101,7 +1099,7 @@ 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 (deprecated)
|
||||
// AzureEnvironment represents an endpoint connected to an Azure environment
|
||||
AzureEnvironment
|
||||
// EdgeAgentEnvironment represents an endpoint connected to an Edge agent
|
||||
EdgeAgentEnvironment
|
||||
|
|
|
@ -254,7 +254,7 @@ paths:
|
|||
- name: "EndpointType"
|
||||
in: "formData"
|
||||
type: "integer"
|
||||
description: "Environment type. Value must be one of: 1 (Docker environment), 2 (Agent environment) or 4 (Edge agent environment)"
|
||||
description: "Environment type. Value must be one of: 1 (Docker environment), 2 (Agent environment), 3 (Azure environment) or 4 (Edge agent environment)"
|
||||
required: true
|
||||
- name: "URL"
|
||||
in: "formData"
|
||||
|
@ -294,6 +294,18 @@ paths:
|
|||
in: "formData"
|
||||
type: "file"
|
||||
description: "TLS client key file"
|
||||
- name: "AzureApplicationID"
|
||||
in: "formData"
|
||||
type: "string"
|
||||
description: "Azure application ID. Required if endpoint type is set to 3"
|
||||
- name: "AzureTenantID"
|
||||
in: "formData"
|
||||
type: "string"
|
||||
description: "Azure tenant ID. Required if endpoint type is set to 3"
|
||||
- name: "AzureAuthenticationKey"
|
||||
in: "formData"
|
||||
type: "string"
|
||||
description: "Azure authentication key. Required if endpoint type is set to 3"
|
||||
responses:
|
||||
200:
|
||||
description: "Success"
|
||||
|
@ -3209,6 +3221,21 @@ definitions:
|
|||
type: "string"
|
||||
example: "/data/tls/key.pem"
|
||||
description: "Path to the TLS client key file"
|
||||
AzureCredentials:
|
||||
type: "object"
|
||||
properties:
|
||||
ApplicationID:
|
||||
type: "string"
|
||||
example: "eag7cdo9-o09l-9i83-9dO9-f0b23oe78db4"
|
||||
description: "Azure application ID"
|
||||
TenantID:
|
||||
type: "string"
|
||||
example: "34ddc78d-4fel-2358-8cc1-df84c8o839f5"
|
||||
description: "Azure tenant ID"
|
||||
AuthenticationKey:
|
||||
type: "string"
|
||||
example: "cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
|
||||
description: "Azure authentication key"
|
||||
LDAPSearchSettings:
|
||||
type: "object"
|
||||
properties:
|
||||
|
@ -3480,7 +3507,7 @@ definitions:
|
|||
Type:
|
||||
type: "integer"
|
||||
example: 1
|
||||
description: "Endpoint environment type. 1 for a Docker environment or 2 for an agent on Docker environment"
|
||||
description: "Endpoint environment type. 1 for a Docker environment, 2 for an agent on Docker environment or 3 for an Azure environment."
|
||||
URL:
|
||||
type: "string"
|
||||
example: "docker.mydomain.tld:2375"
|
||||
|
@ -3509,6 +3536,8 @@ definitions:
|
|||
description: "Team identifier"
|
||||
TLSConfig:
|
||||
$ref: "#/definitions/TLSConfiguration"
|
||||
AzureCredentials:
|
||||
$ref: "#/definitions/AzureCredentials"
|
||||
EndpointSubset:
|
||||
type: "object"
|
||||
properties:
|
||||
|
@ -3523,7 +3552,7 @@ definitions:
|
|||
Type:
|
||||
type: "integer"
|
||||
example: 1
|
||||
description: "Endpoint environment type. 1 for a Docker environment or 2 for an agent on Docker environment"
|
||||
description: "Endpoint environment type. 1 for a Docker environment, 2 for an agent on Docker environment, 3 for an Azure environment."
|
||||
URL:
|
||||
type: "string"
|
||||
example: "docker.mydomain.tld:2375"
|
||||
|
@ -3703,6 +3732,18 @@ definitions:
|
|||
type: "boolean"
|
||||
example: false
|
||||
description: "Skip client verification when using TLS"
|
||||
ApplicationID:
|
||||
type: "string"
|
||||
example: "eag7cdo9-o09l-9i83-9dO9-f0b23oe78db4"
|
||||
description: "Azure application ID"
|
||||
TenantID:
|
||||
type: "string"
|
||||
example: "34ddc78d-4fel-2358-8cc1-df84c8o839f5"
|
||||
description: "Azure tenant ID"
|
||||
AuthenticationKey:
|
||||
type: "string"
|
||||
example: "cOrXoK/1D35w8YQ8nH1/8ZGwzz45JIYD5jxHKXEQknk="
|
||||
description: "Azure authentication key"
|
||||
UserAccessPolicies:
|
||||
$ref: "#/definitions/UserAccessPolicies"
|
||||
TeamAccessPolicies:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue