1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 05:45:22 +02:00

feat(azure): add experimental Azure endpoint support (#1936)

This commit is contained in:
Anthony Lapenna 2018-05-28 16:40:33 +02:00 committed by GitHub
parent 415c6ce5e1
commit 9ad9cc5e2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 1665 additions and 79 deletions

102
api/http/handler/azure.go Normal file
View 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)
}

View file

@ -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)
}

View file

@ -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)
}