2018-06-11 13:13:19 +00:00
package endpoints
import (
2019-07-25 22:38:07 +00:00
"errors"
"net"
2018-06-11 13:13:19 +00:00
"net/http"
2019-07-25 22:38:07 +00:00
"net/url"
2018-07-20 09:02:06 +00:00
"runtime"
2018-06-11 13:13:19 +00:00
"strconv"
2019-09-20 04:14:19 +00:00
"strings"
2018-06-11 13:13:19 +00:00
2018-09-10 10:01:38 +00:00
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
2019-03-21 01:20:14 +00:00
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/http/client"
2018-06-11 13:13:19 +00:00
)
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
AzureApplicationID string
AzureTenantID string
AzureAuthenticationKey string
2020-03-29 09:54:14 +00:00
TagIDs [ ] portainer . TagID
2018-06-11 13:13:19 +00:00
}
func ( payload * endpointCreatePayload ) Validate ( r * http . Request ) error {
name , err := request . RetrieveMultiPartFormValue ( r , "Name" , false )
if err != nil {
2018-09-02 08:35:05 +00:00
return portainer . Error ( "Invalid endpoint name" )
2018-06-11 13:13:19 +00:00
}
payload . Name = name
endpointType , err := request . RetrieveNumericMultiPartFormValue ( r , "EndpointType" , false )
if err != nil || endpointType == 0 {
2019-07-25 22:38:07 +00:00
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)" )
2018-06-11 13:13:19 +00:00
}
payload . EndpointType = endpointType
groupID , _ := request . RetrieveNumericMultiPartFormValue ( r , "GroupID" , true )
if groupID == 0 {
groupID = 1
}
payload . GroupID = groupID
2020-03-29 09:54:14 +00:00
var tagIDs [ ] portainer . TagID
err = request . RetrieveMultiPartFormJSONValue ( r , "TagIds" , & tagIDs , true )
2018-06-15 07:18:25 +00:00
if err != nil {
2020-03-29 09:54:14 +00:00
return portainer . Error ( "Invalid TagIds parameter" )
2018-06-15 07:18:25 +00:00
}
2020-03-29 09:54:14 +00:00
payload . TagIDs = tagIDs
if payload . TagIDs == nil {
payload . TagIDs = make ( [ ] portainer . TagID , 0 )
2018-07-26 08:13:18 +00:00
}
2018-06-15 07:18:25 +00:00
2018-06-11 13:13:19 +00:00
useTLS , _ := request . RetrieveBooleanMultiPartFormValue ( r , "TLS" , true )
payload . TLS = useTLS
if payload . TLS {
skipTLSServerVerification , _ := request . RetrieveBooleanMultiPartFormValue ( r , "TLSSkipVerify" , true )
payload . TLSSkipVerify = skipTLSServerVerification
skipTLSClientVerification , _ := request . RetrieveBooleanMultiPartFormValue ( r , "TLSSkipClientVerify" , true )
payload . TLSSkipClientVerify = skipTLSClientVerification
if ! payload . TLSSkipVerify {
2018-09-10 10:01:38 +00:00
caCert , _ , err := request . RetrieveMultiPartFormFile ( r , "TLSCACertFile" )
2018-06-11 13:13:19 +00:00
if err != nil {
return portainer . Error ( "Invalid CA certificate file. Ensure that the file is uploaded correctly" )
}
payload . TLSCACertFile = caCert
}
if ! payload . TLSSkipClientVerify {
2018-09-10 10:01:38 +00:00
cert , _ , err := request . RetrieveMultiPartFormFile ( r , "TLSCertFile" )
2018-06-11 13:13:19 +00:00
if err != nil {
return portainer . Error ( "Invalid certificate file. Ensure that the file is uploaded correctly" )
}
payload . TLSCertFile = cert
2018-09-10 10:01:38 +00:00
key , _ , err := request . RetrieveMultiPartFormFile ( r , "TLSKeyFile" )
2018-06-11 13:13:19 +00:00
if err != nil {
return portainer . Error ( "Invalid key file. Ensure that the file is uploaded correctly" )
}
payload . TLSKeyFile = key
}
}
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
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 :
2018-07-20 09:02:06 +00:00
url , err := request . RetrieveMultiPartFormValue ( r , "URL" , true )
2018-06-11 13:13:19 +00:00
if err != nil {
return portainer . Error ( "Invalid endpoint URL" )
}
payload . URL = url
publicURL , _ := request . RetrieveMultiPartFormValue ( r , "PublicURL" , true )
payload . PublicURL = publicURL
}
return nil
}
// POST request on /api/endpoints
func ( handler * Handler ) endpointCreate ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
if ! handler . authorizeEndpointManagement {
return & httperror . HandlerError { http . StatusServiceUnavailable , "Endpoint management is disabled" , ErrEndpointManagementDisabled }
}
payload := & endpointCreatePayload { }
err := payload . Validate ( r )
if err != nil {
return & httperror . HandlerError { http . StatusBadRequest , "Invalid request payload" , err }
}
endpoint , endpointCreationError := handler . createEndpoint ( payload )
if endpointCreationError != nil {
return endpointCreationError
}
return response . JSON ( w , endpoint )
}
func ( handler * Handler ) createEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
if portainer . EndpointType ( payload . EndpointType ) == portainer . AzureEnvironment {
return handler . createAzureEndpoint ( payload )
2019-07-25 22:38:07 +00:00
} else if portainer . EndpointType ( payload . EndpointType ) == portainer . EdgeAgentEnvironment {
return handler . createEdgeAgentEndpoint ( payload )
2018-06-11 13:13:19 +00:00
}
if payload . TLS {
return handler . createTLSSecuredEndpoint ( payload )
}
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 }
}
2018-07-24 12:47:19 +00:00
endpointID := handler . EndpointService . GetNextIdentifier ( )
2018-06-11 13:13:19 +00:00
endpoint := & portainer . Endpoint {
2019-05-24 06:04:58 +00:00
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 ,
2020-03-29 09:54:14 +00:00
TagIDs : payload . TagIDs ,
2019-05-24 06:04:58 +00:00
Status : portainer . EndpointStatusUp ,
Snapshots : [ ] portainer . Snapshot { } ,
2018-06-11 13:13:19 +00:00
}
2019-10-07 02:42:01 +00:00
err = handler . saveEndpointAndUpdateAuthorizations ( endpoint )
2019-07-25 22:38:07 +00:00
if err != nil {
2019-10-07 02:42:01 +00:00
return nil , & httperror . HandlerError { http . StatusInternalServerError , "An error occured while trying to create the endpoint" , err }
2019-07-25 22:38:07 +00:00
}
return endpoint , nil
}
func ( handler * Handler ) createEdgeAgentEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
endpointType := portainer . EdgeAgentEnvironment
endpointID := handler . EndpointService . GetNextIdentifier ( )
portainerURL , err := url . Parse ( payload . URL )
if err != nil {
return nil , & httperror . HandlerError { http . StatusBadRequest , "Invalid endpoint URL" , err }
}
portainerHost , _ , err := net . SplitHostPort ( portainerURL . Host )
if err != nil {
portainerHost = portainerURL . Host
}
if portainerHost == "localhost" {
return nil , & httperror . HandlerError { http . StatusBadRequest , "Invalid endpoint URL" , errors . New ( "cannot use localhost as endpoint URL" ) }
}
edgeKey := handler . ReverseTunnelService . GenerateEdgeKey ( payload . URL , portainerHost , endpointID )
endpoint := & portainer . Endpoint {
ID : portainer . EndpointID ( endpointID ) ,
Name : payload . Name ,
URL : portainerHost ,
Type : endpointType ,
GroupID : portainer . EndpointGroupID ( payload . GroupID ) ,
TLSConfig : portainer . TLSConfiguration {
TLS : false ,
} ,
AuthorizedUsers : [ ] portainer . UserID { } ,
AuthorizedTeams : [ ] portainer . TeamID { } ,
Extensions : [ ] portainer . EndpointExtension { } ,
2020-03-29 09:54:14 +00:00
TagIDs : payload . TagIDs ,
2019-07-25 22:38:07 +00:00
Status : portainer . EndpointStatusUp ,
Snapshots : [ ] portainer . Snapshot { } ,
EdgeKey : edgeKey ,
}
2019-10-07 02:42:01 +00:00
err = handler . saveEndpointAndUpdateAuthorizations ( endpoint )
2018-06-11 13:13:19 +00:00
if err != nil {
2019-10-07 02:42:01 +00:00
return nil , & httperror . HandlerError { http . StatusInternalServerError , "An error occured while trying to create the endpoint" , err }
2018-06-11 13:13:19 +00:00
}
return endpoint , nil
}
func ( handler * Handler ) createUnsecuredEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
endpointType := portainer . DockerEnvironment
2018-07-20 09:02:06 +00:00
if payload . URL == "" {
payload . URL = "unix:///var/run/docker.sock"
if runtime . GOOS == "windows" {
payload . URL = "npipe:////./pipe/docker_engine"
}
} else {
2018-06-11 13:13:19 +00:00
agentOnDockerEnvironment , err := client . ExecutePingOperation ( payload . URL , nil )
if err != nil {
return nil , & httperror . HandlerError { http . StatusInternalServerError , "Unable to ping Docker environment" , err }
}
if agentOnDockerEnvironment {
endpointType = portainer . AgentOnDockerEnvironment
}
}
2018-07-24 12:47:19 +00:00
endpointID := handler . EndpointService . GetNextIdentifier ( )
2018-06-11 13:13:19 +00:00
endpoint := & portainer . Endpoint {
2018-07-24 12:47:19 +00:00
ID : portainer . EndpointID ( endpointID ) ,
2018-06-11 13:13:19 +00:00
Name : payload . Name ,
URL : payload . URL ,
Type : endpointType ,
GroupID : portainer . EndpointGroupID ( payload . GroupID ) ,
PublicURL : payload . PublicURL ,
TLSConfig : portainer . TLSConfiguration {
TLS : false ,
} ,
2019-05-24 06:04:58 +00:00
UserAccessPolicies : portainer . UserAccessPolicies { } ,
TeamAccessPolicies : portainer . TeamAccessPolicies { } ,
Extensions : [ ] portainer . EndpointExtension { } ,
2020-03-29 09:54:14 +00:00
TagIDs : payload . TagIDs ,
2019-05-24 06:04:58 +00:00
Status : portainer . EndpointStatusUp ,
Snapshots : [ ] portainer . Snapshot { } ,
2018-06-11 13:13:19 +00:00
}
2018-07-23 07:51:33 +00:00
err := handler . snapshotAndPersistEndpoint ( endpoint )
2018-06-11 13:13:19 +00:00
if err != nil {
2018-07-23 07:51:33 +00:00
return nil , err
2018-06-11 13:13:19 +00:00
}
return endpoint , nil
}
func ( handler * Handler ) createTLSSecuredEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
tlsConfig , err := crypto . CreateTLSConfigurationFromBytes ( payload . TLSCACertFile , payload . TLSCertFile , payload . TLSKeyFile , payload . TLSSkipClientVerify , payload . TLSSkipVerify )
if err != nil {
return nil , & httperror . HandlerError { http . StatusInternalServerError , "Unable to create TLS configuration" , err }
}
agentOnDockerEnvironment , err := client . ExecutePingOperation ( payload . URL , tlsConfig )
if err != nil {
return nil , & httperror . HandlerError { http . StatusInternalServerError , "Unable to ping Docker environment" , err }
}
endpointType := portainer . DockerEnvironment
if agentOnDockerEnvironment {
endpointType = portainer . AgentOnDockerEnvironment
}
2018-07-24 12:47:19 +00:00
endpointID := handler . EndpointService . GetNextIdentifier ( )
2018-06-11 13:13:19 +00:00
endpoint := & portainer . Endpoint {
2018-07-24 12:47:19 +00:00
ID : portainer . EndpointID ( endpointID ) ,
2018-06-11 13:13:19 +00:00
Name : payload . Name ,
URL : payload . URL ,
Type : endpointType ,
GroupID : portainer . EndpointGroupID ( payload . GroupID ) ,
PublicURL : payload . PublicURL ,
TLSConfig : portainer . TLSConfiguration {
TLS : payload . TLS ,
TLSSkipVerify : payload . TLSSkipVerify ,
} ,
2019-05-24 06:04:58 +00:00
UserAccessPolicies : portainer . UserAccessPolicies { } ,
TeamAccessPolicies : portainer . TeamAccessPolicies { } ,
Extensions : [ ] portainer . EndpointExtension { } ,
2020-03-29 09:54:14 +00:00
TagIDs : payload . TagIDs ,
2019-05-24 06:04:58 +00:00
Status : portainer . EndpointStatusUp ,
Snapshots : [ ] portainer . Snapshot { } ,
2018-06-11 13:13:19 +00:00
}
filesystemError := handler . storeTLSFiles ( endpoint , payload )
if err != nil {
return nil , filesystemError
}
2018-07-24 12:47:19 +00:00
endpointCreationError := handler . snapshotAndPersistEndpoint ( endpoint )
if endpointCreationError != nil {
return nil , endpointCreationError
2018-06-11 13:13:19 +00:00
}
return endpoint , nil
}
2018-07-23 07:51:33 +00:00
func ( handler * Handler ) snapshotAndPersistEndpoint ( endpoint * portainer . Endpoint ) * httperror . HandlerError {
snapshot , err := handler . Snapshotter . CreateSnapshot ( endpoint )
endpoint . Status = portainer . EndpointStatusUp
if err != nil {
2019-09-20 04:14:19 +00:00
if strings . Contains ( err . Error ( ) , "Invalid request signature" ) {
err = errors . New ( "agent already paired with another Portainer instance" )
}
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to initiate communications with endpoint" , err }
2018-07-23 07:51:33 +00:00
}
if snapshot != nil {
endpoint . Snapshots = [ ] portainer . Snapshot { * snapshot }
}
2019-10-07 02:42:01 +00:00
err = handler . saveEndpointAndUpdateAuthorizations ( endpoint )
2018-07-23 07:51:33 +00:00
if err != nil {
2019-10-07 02:42:01 +00:00
return & httperror . HandlerError { http . StatusInternalServerError , "An error occured while trying to create the endpoint" , err }
}
return nil
}
func ( handler * Handler ) saveEndpointAndUpdateAuthorizations ( endpoint * portainer . Endpoint ) error {
err := handler . EndpointService . CreateEndpoint ( endpoint )
if err != nil {
return err
}
group , err := handler . EndpointGroupService . EndpointGroup ( endpoint . GroupID )
if err != nil {
return err
}
if len ( group . UserAccessPolicies ) > 0 || len ( group . TeamAccessPolicies ) > 0 {
return handler . AuthorizationService . UpdateUsersAuthorizations ( )
2018-07-23 07:51:33 +00:00
}
return nil
}
2018-06-11 13:13:19 +00:00
func ( handler * Handler ) storeTLSFiles ( endpoint * portainer . Endpoint , payload * endpointCreatePayload ) * httperror . HandlerError {
folder := strconv . Itoa ( int ( endpoint . ID ) )
if ! payload . TLSSkipVerify {
caCertPath , err := handler . FileService . StoreTLSFileFromBytes ( folder , portainer . TLSFileCA , payload . TLSCACertFile )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to persist TLS CA certificate file on disk" , err }
}
endpoint . TLSConfig . TLSCACertPath = caCertPath
}
if ! payload . TLSSkipClientVerify {
certPath , err := handler . FileService . StoreTLSFileFromBytes ( folder , portainer . TLSFileCert , payload . TLSCertFile )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to persist TLS certificate file on disk" , err }
}
endpoint . TLSConfig . TLSCertPath = certPath
keyPath , err := handler . FileService . StoreTLSFileFromBytes ( folder , portainer . TLSFileKey , payload . TLSKeyFile )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to persist TLS key file on disk" , err }
}
endpoint . TLSConfig . TLSKeyPath = keyPath
}
return nil
}