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/http/client"
2020-06-16 07:58:16 +00:00
"github.com/portainer/portainer/api/internal/edge"
2018-06-11 13:13:19 +00:00
)
type endpointCreatePayload struct {
2020-06-09 02:43:32 +00:00
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
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 {
2020-07-07 21:57:52 +00:00
return errors . New ( "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 {
2020-07-07 21:57:52 +00:00
return errors . New ( "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-07-07 21:57:52 +00:00
return errors . New ( "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 {
2020-07-07 21:57:52 +00:00
return errors . New ( "Invalid CA certificate file. Ensure that the file is uploaded correctly" )
2018-06-11 13:13:19 +00:00
}
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 {
2020-07-07 21:57:52 +00:00
return errors . New ( "Invalid certificate file. Ensure that the file is uploaded correctly" )
2018-06-11 13:13:19 +00:00
}
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 {
2020-07-07 21:57:52 +00:00
return errors . New ( "Invalid key file. Ensure that the file is uploaded correctly" )
2018-06-11 13:13:19 +00:00
}
payload . TLSKeyFile = key
}
}
2020-06-09 02:43:32 +00:00
switch portainer . EndpointType ( payload . EndpointType ) {
case portainer . AzureEnvironment :
azureApplicationID , err := request . RetrieveMultiPartFormValue ( r , "AzureApplicationID" , false )
if err != nil {
2020-07-07 21:57:52 +00:00
return errors . New ( "Invalid Azure application ID" )
2020-06-09 02:43:32 +00:00
}
payload . AzureApplicationID = azureApplicationID
azureTenantID , err := request . RetrieveMultiPartFormValue ( r , "AzureTenantID" , false )
if err != nil {
2020-07-07 21:57:52 +00:00
return errors . New ( "Invalid Azure tenant ID" )
2020-06-09 02:43:32 +00:00
}
payload . AzureTenantID = azureTenantID
2020-05-19 03:08:57 +00:00
2020-06-09 02:43:32 +00:00
azureAuthenticationKey , err := request . RetrieveMultiPartFormValue ( r , "AzureAuthenticationKey" , false )
if err != nil {
2020-07-07 21:57:52 +00:00
return errors . New ( "Invalid Azure authentication key" )
2020-06-09 02:43:32 +00:00
}
payload . AzureAuthenticationKey = azureAuthenticationKey
default :
2020-07-05 23:21:03 +00:00
endpointURL , err := request . RetrieveMultiPartFormValue ( r , "URL" , true )
2020-06-09 02:43:32 +00:00
if err != nil {
2020-07-07 21:57:52 +00:00
return errors . New ( "Invalid endpoint URL" )
2020-06-09 02:43:32 +00:00
}
2020-07-05 23:21:03 +00:00
payload . URL = endpointURL
2020-06-09 02:43:32 +00:00
publicURL , _ := request . RetrieveMultiPartFormValue ( r , "PublicURL" , true )
payload . PublicURL = publicURL
}
2018-06-11 13:13:19 +00:00
2020-06-04 05:35:09 +00:00
checkinInterval , _ := request . RetrieveNumericMultiPartFormValue ( r , "CheckinInterval" , true )
payload . EdgeCheckinInterval = checkinInterval
2018-06-11 13:13:19 +00:00
return nil
}
// POST request on /api/endpoints
func ( handler * Handler ) endpointCreate ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
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
}
2020-05-20 05:23:15 +00:00
endpointGroup , err := handler . DataStore . EndpointGroup ( ) . EndpointGroup ( endpoint . GroupID )
2020-05-14 02:14:28 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to find an endpoint group inside the database" , err }
}
2020-05-20 05:23:15 +00:00
edgeGroups , err := handler . DataStore . EdgeGroup ( ) . EdgeGroups ( )
2020-05-14 02:14:28 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve edge groups from the database" , err }
}
2020-05-20 05:23:15 +00:00
edgeStacks , err := handler . DataStore . EdgeStack ( ) . EdgeStacks ( )
2020-05-14 02:14:28 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve edge stacks from the database" , err }
}
relationObject := & portainer . EndpointRelation {
EndpointID : endpoint . ID ,
EdgeStacks : map [ portainer . EdgeStackID ] bool { } ,
}
2020-07-05 23:21:03 +00:00
if endpoint . Type == portainer . EdgeAgentOnDockerEnvironment || endpoint . Type == portainer . EdgeAgentOnKubernetesEnvironment {
2020-06-16 07:58:16 +00:00
relatedEdgeStacks := edge . EndpointRelatedEdgeStacks ( endpoint , endpointGroup , edgeGroups , edgeStacks )
2020-05-14 02:14:28 +00:00
for _ , stackID := range relatedEdgeStacks {
relationObject . EdgeStacks [ stackID ] = true
}
}
2020-05-20 05:23:15 +00:00
err = handler . DataStore . EndpointRelation ( ) . CreateEndpointRelation ( relationObject )
2020-05-14 02:14:28 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to persist the relation object inside the database" , err }
}
2018-06-11 13:13:19 +00:00
return response . JSON ( w , endpoint )
}
func ( handler * Handler ) createEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
2020-07-05 23:21:03 +00:00
switch portainer . EndpointType ( payload . EndpointType ) {
case portainer . AzureEnvironment :
2020-06-09 02:43:32 +00:00
return handler . createAzureEndpoint ( payload )
2020-07-05 23:21:03 +00:00
case portainer . EdgeAgentOnDockerEnvironment :
return handler . createEdgeAgentEndpoint ( payload , portainer . EdgeAgentOnDockerEnvironment )
case portainer . KubernetesLocalEnvironment :
return handler . createKubernetesEndpoint ( payload )
case portainer . EdgeAgentOnKubernetesEnvironment :
return handler . createEdgeAgentEndpoint ( payload , portainer . EdgeAgentOnKubernetesEnvironment )
2018-06-11 13:13:19 +00:00
}
if payload . TLS {
2020-07-05 23:21:03 +00:00
return handler . createTLSSecuredEndpoint ( payload , portainer . EndpointType ( payload . EndpointType ) )
2018-06-11 13:13:19 +00:00
}
return handler . createUnsecuredEndpoint ( payload )
}
2020-06-09 02:43:32 +00:00
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 ,
2020-07-05 23:21:03 +00:00
Snapshots : [ ] portainer . DockerSnapshot { } ,
Kubernetes : portainer . KubernetesDefault ( ) ,
2020-06-09 02:43:32 +00:00
}
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
}
2020-07-05 23:21:03 +00:00
func ( handler * Handler ) createEdgeAgentEndpoint ( payload * endpointCreatePayload , endpointType portainer . EndpointType ) ( * portainer . Endpoint , * httperror . HandlerError ) {
2020-05-20 05:23:15 +00:00
endpointID := handler . DataStore . Endpoint ( ) . GetNextIdentifier ( )
2019-07-25 22:38:07 +00:00
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 ,
} ,
2020-06-04 05:35:09 +00:00
AuthorizedUsers : [ ] portainer . UserID { } ,
AuthorizedTeams : [ ] portainer . TeamID { } ,
Extensions : [ ] portainer . EndpointExtension { } ,
TagIDs : payload . TagIDs ,
Status : portainer . EndpointStatusUp ,
2020-07-05 23:21:03 +00:00
Snapshots : [ ] portainer . DockerSnapshot { } ,
2020-06-04 05:35:09 +00:00
EdgeKey : edgeKey ,
EdgeCheckinInterval : payload . EdgeCheckinInterval ,
2020-07-05 23:21:03 +00:00
Kubernetes : portainer . KubernetesDefault ( ) ,
2019-07-25 22:38:07 +00:00
}
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"
}
2018-06-11 13:13:19 +00:00
}
2020-05-20 05:23:15 +00:00
endpointID := handler . DataStore . Endpoint ( ) . 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 ,
2020-07-05 23:21:03 +00:00
Snapshots : [ ] portainer . DockerSnapshot { } ,
Kubernetes : portainer . KubernetesDefault ( ) ,
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
}
2020-07-05 23:21:03 +00:00
func ( handler * Handler ) createKubernetesEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
if payload . URL == "" {
payload . URL = "https://kubernetes.default.svc"
2018-06-11 13:13:19 +00:00
}
2020-07-05 23:21:03 +00:00
endpointID := handler . DataStore . Endpoint ( ) . GetNextIdentifier ( )
endpoint := & portainer . Endpoint {
ID : portainer . EndpointID ( endpointID ) ,
Name : payload . Name ,
URL : payload . URL ,
Type : portainer . KubernetesLocalEnvironment ,
GroupID : portainer . EndpointGroupID ( payload . GroupID ) ,
PublicURL : payload . PublicURL ,
TLSConfig : portainer . TLSConfiguration {
TLS : payload . TLS ,
TLSSkipVerify : payload . TLSSkipVerify ,
} ,
UserAccessPolicies : portainer . UserAccessPolicies { } ,
TeamAccessPolicies : portainer . TeamAccessPolicies { } ,
Extensions : [ ] portainer . EndpointExtension { } ,
TagIDs : payload . TagIDs ,
Status : portainer . EndpointStatusUp ,
Snapshots : [ ] portainer . DockerSnapshot { } ,
Kubernetes : portainer . KubernetesDefault ( ) ,
2018-06-11 13:13:19 +00:00
}
2020-07-05 23:21:03 +00:00
err := handler . snapshotAndPersistEndpoint ( endpoint )
if err != nil {
return nil , err
2018-06-11 13:13:19 +00:00
}
2020-07-05 23:21:03 +00:00
return endpoint , nil
}
func ( handler * Handler ) createTLSSecuredEndpoint ( payload * endpointCreatePayload , endpointType portainer . EndpointType ) ( * portainer . Endpoint , * httperror . HandlerError ) {
2020-05-20 05:23:15 +00:00
endpointID := handler . DataStore . Endpoint ( ) . 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 ,
2020-07-05 23:21:03 +00:00
Snapshots : [ ] portainer . DockerSnapshot { } ,
Kubernetes : portainer . KubernetesDefault ( ) ,
2018-06-11 13:13:19 +00:00
}
2020-07-05 23:21:03 +00:00
err := handler . storeTLSFiles ( endpoint , payload )
2018-06-11 13:13:19 +00:00
if err != nil {
2020-07-05 23:21:03 +00:00
return nil , err
2018-06-11 13:13:19 +00:00
}
2020-07-05 23:21:03 +00:00
err = handler . snapshotAndPersistEndpoint ( endpoint )
if err != nil {
return nil , err
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 {
2020-07-05 23:21:03 +00:00
err := handler . SnapshotService . SnapshotEndpoint ( endpoint )
2018-07-23 07:51:33 +00:00
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
}
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 {
2020-05-20 05:23:15 +00:00
err := handler . DataStore . Endpoint ( ) . CreateEndpoint ( endpoint )
2019-10-07 02:42:01 +00:00
if err != nil {
return err
}
2020-05-20 05:23:15 +00:00
group , err := handler . DataStore . EndpointGroup ( ) . EndpointGroup ( endpoint . GroupID )
2019-10-07 02:42:01 +00:00
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
}
2020-05-14 02:14:28 +00:00
for _ , tagID := range endpoint . TagIDs {
2020-05-20 05:23:15 +00:00
tag , err := handler . DataStore . Tag ( ) . Tag ( tagID )
2020-05-14 02:14:28 +00:00
if err != nil {
return err
}
tag . Endpoints [ endpoint . ID ] = true
2020-05-20 05:23:15 +00:00
err = handler . DataStore . Tag ( ) . UpdateTag ( tagID , tag )
2020-05-14 02:14:28 +00:00
if err != nil {
return err
}
}
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
}