Skip to content

Commit

Permalink
Merge pull request #58 from reubenmiller/feat-remote-access-client
Browse files Browse the repository at this point in the history
feat: remote access (local proxy) client
  • Loading branch information
reubenmiller authored May 18, 2024
2 parents 40cf6be + 19b15ab commit 967f0ed
Show file tree
Hide file tree
Showing 10 changed files with 487 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ run:

linters-settings:
govet:
check-shadowing: true
shadow: true
fieldalignment: true
gofmt:
simplify: true
Expand Down
12 changes: 7 additions & 5 deletions pkg/c8y/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ type Client struct {
Identity *IdentityService
Microservice *MicroserviceService
Notification2 *Notification2Service
RemoteAccess *RemoteAccessService
Retention *RetentionRuleService
TenantOptions *TenantOptionsService
Software *InventorySoftwareService
Expand Down Expand Up @@ -345,6 +346,7 @@ func NewClient(httpClient *http.Client, baseURL string, tenant string, username
c.Microservice = (*MicroserviceService)(&c.common)
c.Notification2 = (*Notification2Service)(&c.common)
c.Context = (*ContextService)(&c.common)
c.RemoteAccess = (*RemoteAccessService)(&c.common)
c.Retention = (*RetentionRuleService)(&c.common)
c.TenantOptions = (*TenantOptionsService)(&c.common)
c.Software = (*InventorySoftwareService)(&c.common)
Expand Down Expand Up @@ -711,7 +713,7 @@ func (c *Client) SendRequest(ctx context.Context, options RequestOptions) (*Resp
return nil, dryRunErr
}

localLogger.Info(c.hideSensitiveInformationIfActive(fmt.Sprintf("Headers: %v", req.Header)))
localLogger.Info(c.HideSensitiveInformationIfActive(fmt.Sprintf("Headers: %v", req.Header)))

if options.PrepareRequest != nil {
req, err = options.PrepareRequest(req)
Expand Down Expand Up @@ -934,7 +936,7 @@ func (c *Client) SetBasicAuthorization(req *http.Request) {
} else {
headerUsername = c.Username
}
Logger.Infof("Current username: %s", c.hideSensitiveInformationIfActive(headerUsername))
Logger.Infof("Current username: %s", c.HideSensitiveInformationIfActive(headerUsername))
req.SetBasicAuth(headerUsername, c.Password)
}

Expand Down Expand Up @@ -1100,7 +1102,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}, middl
}

if req != nil {
Logger.Infof("Sending request: %s %s", req.Method, c.hideSensitiveInformationIfActive(req.URL.String()))
Logger.Infof("Sending request: %s %s", req.Method, c.HideSensitiveInformationIfActive(req.URL.String()))
}

// Log the body (if applicable)
Expand Down Expand Up @@ -1287,7 +1289,7 @@ func CheckResponse(r *http.Response) error {
return errorResponse
}

func (c *Client) hideSensitiveInformationIfActive(message string) string {
func (c *Client) HideSensitiveInformationIfActive(message string) string {

if strings.ToLower(os.Getenv(EnvVarLoggerHideSensitive)) != "true" {
return message
Expand Down Expand Up @@ -1386,7 +1388,7 @@ func (c *Client) DefaultDryRunHandler(options *RequestOptions, req *http.Request
}
}

Logger.Info(c.hideSensitiveInformationIfActive(message))
Logger.Info(c.HideSensitiveInformationIfActive(message))
}

type CumulocityTokenClaim struct {
Expand Down
108 changes: 108 additions & 0 deletions pkg/c8y/remoteaccess.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package c8y

import (
"context"
"fmt"
"net/http"
)

const (
RemoteAccessProtocolPassthrough = "PASSTHROUGH"
RemoteAccessProtocolSSH = "SSH"
RemoteAccessProtocolVNC = "VNC"
RemoteAccessProtocolTelnet = "TELNET"
)

// RemoteAccessService
type RemoteAccessService service

// RemoteAccessCollectionOptions remote access collection filter options
type RemoteAccessCollectionOptions struct {
// Pagination options
PaginationOptions
}

// RemoteAccessCollection collection of remote access configurations
type RemoteAccessConfiguration struct {
ID string `json:"id"`
Name string `json:"name"`
Hostname string `json:"hostname"`
Port int `json:"port"`
Protocol string `json:"protocol"`
Credentials RemoteAccessCredentials `json:"credentials"`
}

// RemoteAccessCredentials
type RemoteAccessCredentials struct {
Type string `json:"type"`
Username string `json:"username"`
Password string `json:"password"`
PublicKey string `json:"publicKey"`
PrivateKey string `json:"privateKey"`
HostKey string `json:"hostKey"`
}

func (s *RemoteAccessService) path(mo_id string) string {
return fmt.Sprintf("/service/remoteaccess/devices/%s/configurations", mo_id)
}
func (s *RemoteAccessService) config_path(mo_id string, config_id string) string {
return fmt.Sprintf("/service/remoteaccess/devices/%s/configurations/%s", mo_id, config_id)
}

// GetConfiguration return a specific remote access configuration for a given device
func (s *RemoteAccessService) GetConfiguration(ctx context.Context, mo_id, config_id string) (*RemoteAccessConfiguration, *Response, error) {
data := new(RemoteAccessConfiguration)
resp, err := s.client.SendRequest(ctx, RequestOptions{
Method: http.MethodGet,
Path: s.config_path(mo_id, config_id),
ResponseData: data,
})
return data, resp, err
}

// GetConfigurations returns a collection of Cumulocity remote access configurations
// for a given managed object
func (s *RemoteAccessService) GetConfigurations(ctx context.Context, mo_id string, opt *RemoteAccessCollectionOptions) ([]RemoteAccessConfiguration, *Response, error) {
data := make([]RemoteAccessConfiguration, 0)
resp, err := s.client.SendRequest(ctx, RequestOptions{
Method: http.MethodGet,
Path: s.path(mo_id),
Query: opt,
ResponseData: &data,
})
return data, resp, err
}

// DeleteConfiguration delete remote access configuration
func (s *RemoteAccessService) DeleteConfiguration(ctx context.Context, mo_id string, config_id string, opt *RemoteAccessCollectionOptions) (*Response, error) {
resp, err := s.client.SendRequest(ctx, RequestOptions{
Method: http.MethodDelete,
Path: s.config_path(mo_id, config_id),
Query: opt,
})
return resp, err
}

// Create creates a new operation for a device
func (s *RemoteAccessService) Create(ctx context.Context, mo_id string, config_id string, body interface{}) (*RemoteAccessConfiguration, *Response, error) {
data := new(RemoteAccessConfiguration)
resp, err := s.client.SendRequest(ctx, RequestOptions{
Method: http.MethodPost,
Path: s.config_path(mo_id, config_id),
Body: body,
ResponseData: data,
})
return data, resp, err
}

// Update updates a Cumulocity operation
func (s *RemoteAccessService) Update(ctx context.Context, mo_id string, config_id string, body *OperationUpdateOptions) (*Operation, *Response, error) {
data := new(Operation)
resp, err := s.client.SendRequest(ctx, RequestOptions{
Method: http.MethodPut,
Path: s.config_path(mo_id, config_id),
Body: body,
ResponseData: data,
})
return data, resp, err
}
91 changes: 91 additions & 0 deletions pkg/proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package proxy

import (
"io"
"net"

"github.com/gorilla/websocket"
"github.com/reubenmiller/go-c8y/pkg/c8y"
"github.com/reubenmiller/go-c8y/pkg/wsconnadapter"
)

func chanFromConn(conn io.Reader) chan []byte {
c := make(chan []byte)

go func() {
b := make([]byte, 1024)

for {
n, err := conn.Read(b)
if n > 0 {
res := make([]byte, n)
// Copy the buffer so it doesn't get changed while read by the recipient.
copy(res, b[:n])
c <- res
}
if err != nil {
c <- nil
break
}
}
}()

return c
}

// Copy accepts a websocket connection and TCP connection and copies data between them
func Copy(gwsConn *websocket.Conn, tcpConn net.Conn) {
wsConn := wsconnadapter.New(gwsConn)
wsChan := chanFromConn(wsConn)
tcpChan := chanFromConn(tcpConn)

defer wsConn.Close()
defer tcpConn.Close()
for {
select {
case wsData := <-wsChan:
if wsData == nil {
c8y.Logger.Infof("Connection closed: D: %v, S: %v", tcpConn.LocalAddr(), wsConn.RemoteAddr())
return
} else {
tcpConn.Write(wsData)
}
case tcpData := <-tcpChan:
if tcpData == nil {
c8y.Logger.Infof("Connection closed: D: %v, S: %v", tcpConn.LocalAddr(), wsConn.LocalAddr())
return
} else {
wsConn.Write(tcpData)
}
}
}

}

// Copy accepts a websocket connection and read/writer and copies data between them
func CopyReadWriter(gwsConn *websocket.Conn, r io.ReadCloser, w io.Writer) {
wsConn := wsconnadapter.New(gwsConn)
wsChan := chanFromConn(wsConn)
stdioChan := chanFromConn(r)

defer wsConn.Close()
defer r.Close()
for {
select {
case wsData := <-wsChan:
if wsData == nil {
c8y.Logger.Infof("STDIO connection closed: D: %v, S: %v", "stdio", wsConn.RemoteAddr())
return
} else {
w.Write(wsData)
}
case tcpData := <-stdioChan:
if tcpData == nil {
c8y.Logger.Infof("STDIO connection closed: D: %v, S: %v", "stdio", wsConn.LocalAddr())
return
} else {
wsConn.Write(tcpData)
}
}
}
}
Loading

0 comments on commit 967f0ed

Please sign in to comment.