Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

Commit

Permalink
Endpoint plugins configurable via YAML (#4751)
Browse files Browse the repository at this point in the history
* Add proxy single request API endpoint

* Add endpoint plugin generation from a YAML file

* Deploy to CF from private GitHub and GitLab repos

* Use subtypes for YAML generated endpoints

* Nicer YAML format; ignore unknown endpoint types in the DB
  • Loading branch information
ikapelyukhin committed Nov 19, 2020
1 parent 069b4df commit bb3a4f4
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 9 deletions.
19 changes: 19 additions & 0 deletions src/jetstream/http_basic_requests.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"encoding/base64"
"errors"
"net/http"

log "github.com/sirupsen/logrus"
Expand All @@ -20,3 +22,20 @@ func (p *portalProxy) doHttpBasicFlowRequest(cnsiRequest *interfaces.CNSIRequest
return p.DoAuthFlowRequest(cnsiRequest, req, authHandler)

}

func (p *portalProxy) doTokenBearerFlowRequest(cnsiRequest *interfaces.CNSIRequest, req *http.Request) (*http.Response, error) {
log.Debug("doTokenBearerFlowRequest")

authHandler := func(tokenRec interfaces.TokenRecord, cnsi interfaces.CNSIRecord) (*http.Response, error) {
authTokenDecodedBytes, err := base64.StdEncoding.DecodeString(tokenRec.AuthToken)
if err != nil {
return nil, errors.New("Failed to decode auth token")
}

// Token auth has no token refresh or expiry - so much simpler than the OAuth flow
req.Header.Set("Authorization", "bearer "+string(authTokenDecodedBytes))
client := p.GetHttpClientForRequest(req, cnsi.SkipSSLValidation)
return client.Do(req)
}
return p.DoAuthFlowRequest(cnsiRequest, req, authHandler)
}
10 changes: 9 additions & 1 deletion src/jetstream/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces"
"github.com/labstack/echo/v4"
log "github.com/sirupsen/logrus"
)

// Endpoint - This represents the CNSI endpoint
Expand Down Expand Up @@ -97,7 +98,14 @@ func (p *portalProxy) getInfo(c echo.Context) (*interfaces.Info, error) {
endpoint.SystemSharedToken = token.SystemShared
}
cnsiType := cnsi.CNSIType
s.Endpoints[cnsiType][cnsi.GUID] = endpoint

_, ok = s.Endpoints[cnsiType]
if ok {
s.Endpoints[cnsiType][cnsi.GUID] = endpoint
} else {
// definitions of YAML-defined plugins may be removed
log.Warnf("Unknown endpoint type %q encountered in the DB", cnsiType)
}
}

// Allow plugin to modify the info data
Expand Down
4 changes: 4 additions & 0 deletions src/jetstream/load_plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ package main
import (
"github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces"
log "github.com/sirupsen/logrus"

"github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/yamlgenerated"
)

func (pp *portalProxy) loadPlugins() {

pp.Plugins = make(map[string]interfaces.StratosPlugin)
log.Info("Initialising plugins")

yamlgenerated.MakePluginsFromConfig()

for name := range interfaces.PluginInits {
addPlugin(pp, name)
}
Expand Down
14 changes: 14 additions & 0 deletions src/jetstream/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,15 @@ func newPortalProxy(pc interfaces.PortalConfig, dcp *sql.DB, ss HttpSessionStore
UserInfo: pp.GetCNSIUserFromBasicToken,
})

// Generic Token Bearer Auth
pp.AddAuthProvider(interfaces.AuthTypeBearer, interfaces.AuthProvider{
Handler: pp.doTokenBearerFlowRequest,
UserInfo: func(cnsiGUID string, cfTokenRecord *interfaces.TokenRecord) (*interfaces.ConnectedUser, bool) {
// don't fetch user info for the generic token auth
return &interfaces.ConnectedUser{}, false
},
})

// OIDC
pp.AddAuthProvider(interfaces.AuthTypeOIDC, interfaces.AuthProvider{
Handler: pp.DoOidcFlowRequest,
Expand Down Expand Up @@ -1025,6 +1034,11 @@ func (p *portalProxy) registerRoutes(e *echo.Echo, needSetupMiddleware bool) {
// Proxy single request
stableAPIGroup.GET("/proxy/:uuid/*", p.ProxySingleRequest)

sessionAuthGroup := sessionGroup.Group("/auth")

// Connect to Endpoint (SSO)
sessionAuthGroup.GET("/tokens", p.ssoLoginToCNSI)

// Info
sessionGroup.GET("/info", p.info)

Expand Down
5 changes: 5 additions & 0 deletions src/jetstream/plugins.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- name: git.private_github
auth_type: HttpBasic
- name: git.private_gitlab
auth_type: Bearer

64 changes: 61 additions & 3 deletions src/jetstream/plugins/cfapppush/deploy.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package cfapppush

import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -68,6 +70,11 @@ const (
OVERRIDES_SUPPLIED
)

const (
SCM_TYPE_GITHUB = "github"
SCM_TYPE_GITLAB = "gitlab"
)

const (
stratosProjectKey = "STRATOS_PROJECT"
)
Expand All @@ -82,6 +89,7 @@ func (cfAppPush *CFAppPush) deploy(echoContext echo.Context) error {

// App ID is this is a redeploy
appID := echoContext.QueryParam("app")
userGUID := echoContext.Get("user_id").(string)

log.Debug("UpgradeToWebSocket")
clientWebSocket, pingTicker, err := interfaces.UpgradeToWebSocket(echoContext)
Expand Down Expand Up @@ -121,7 +129,7 @@ func (cfAppPush *CFAppPush) deploy(echoContext echo.Context) error {
// Get the source, depending on the source type
switch msg.Type {
case SOURCE_GITSCM:
stratosProject, appDir, err = getGitSCMSource(clientWebSocket, tempDir, msg)
stratosProject, appDir, err = cfAppPush.getGitSCMSource(clientWebSocket, tempDir, msg, userGUID)
case SOURCE_FOLDER:
stratosProject, appDir, err = getFolderSource(clientWebSocket, tempDir, msg)
case SOURCE_GITURL:
Expand Down Expand Up @@ -369,7 +377,7 @@ func getFolderSource(clientWebSocket *websocket.Conn, tempDir string, msg Socket
return stratosProject, tempDir, nil
}

func getGitSCMSource(clientWebSocket *websocket.Conn, tempDir string, msg SocketMessage) (StratosProject, string, error) {
func (cfAppPush *CFAppPush) getGitSCMSource(clientWebSocket *websocket.Conn, tempDir string, msg SocketMessage, userGUID string) (StratosProject, string, error) {
var (
err error
)
Expand All @@ -380,7 +388,57 @@ func getGitSCMSource(clientWebSocket *websocket.Conn, tempDir string, msg Socket
return StratosProject{}, tempDir, err
}

log.Debugf("GitSCM SCM: %s, Source: %s, branch %s, url: %s", info.SCM, info.Project, info.Branch, info.URL)
loggerURL := info.URL

if len(info.EndpointGUID) != 0 {
tokenRecord, isTokenFound := cfAppPush.portalProxy.GetCNSITokenRecord(info.EndpointGUID, userGUID)
if isTokenFound != true {
err := fmt.Errorf("No token found for endpoint %s", info.EndpointGUID)
log.Errorf("%+v", err)
return StratosProject{}, tempDir, err
}

authTokenDecodedBytes, err := base64.StdEncoding.DecodeString(tokenRecord.AuthToken)
if err != nil {
return StratosProject{}, tempDir, errors.New("Failed to decode auth token")
}

parsedURL, err := url.Parse(info.URL)
if err != nil {
return StratosProject{}, tempDir, errors.New("Failed to parse SCM URL")
}

var (
username string
password string
)

switch info.SCM {
case SCM_TYPE_GITHUB:
// GitHub API uses basic HTTP auth, username and password are stored in the DB
pieces := strings.SplitN(string(authTokenDecodedBytes), ":", 2)
username, password = pieces[0], pieces[1]
case SCM_TYPE_GITLAB:
// GitLab API uses bearer token auth, the username is supplied by the frontend
username = info.Username
password = string(authTokenDecodedBytes)
default:
return StratosProject{}, tempDir, fmt.Errorf("Unknown SCM type '%s'", info.SCM)
}

if len(username) == 0 {
return StratosProject{}, tempDir, errors.New("Username is empty")
}

// mask the credentials for the logs
parsedURL.User = url.UserPassword("REDACTED", "REDACTED")
loggerURL = parsedURL.String()

parsedURL.User = url.UserPassword(username, password)
info.URL = parsedURL.String()
}

log.Debugf("GitSCM SCM: %s, Source: %s, branch %s, url: %s", info.SCM, info.Project, info.Branch, loggerURL)
cloneDetails := CloneDetails{
Url: info.URL,
Branch: info.Branch,
Expand Down
12 changes: 7 additions & 5 deletions src/jetstream/plugins/cfapppush/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ type DeploySource struct {
// Structure used to provide metadata about the GitHub source
type GitSCMSourceInfo struct {
DeploySource
Project string `json:"project"`
Branch string `json:"branch"`
URL string `json:"url"`
CommitHash string `json:"commit"`
SCM string `json:"scm"`
Project string `json:"project"`
Branch string `json:"branch"`
URL string `json:"url"`
CommitHash string `json:"commit"`
SCM string `json:"scm"`
EndpointGUID string `json:"endpoint_guid"` // credentials of which to use, e.g. of a private GitHub instance
Username string `json:"username"` // GitLab username has to be supplied by the frontend
}

// Structure used to provide metadata about the Git Url source
Expand Down
Loading

0 comments on commit bb3a4f4

Please sign in to comment.