Skip to content

Commit

Permalink
feat: Add CMD protocol that SSH's and execs the command
Browse files Browse the repository at this point in the history
  • Loading branch information
whiskeyjimbo committed Jan 8, 2025
1 parent 368e74d commit c880392
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/expr-lang/expr v1.16.9
go.uber.org/automaxprocs v1.6.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.32.0
)

require (
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
3 changes: 3 additions & 0 deletions internal/checkers/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
HTTPS Protocol = "HTTPS"
SMTP Protocol = "SMTP"
DNS Protocol = "DNS"
CMD Protocol = "CMD"
)

type CheckResult struct {
Expand All @@ -40,6 +41,8 @@ func NewChecker(protocol Protocol) (Checker, error) {
return NewSMTPChecker(), nil
case DNS:
return NewDNSChecker(), nil
case CMD:
return NewCMDChecker(), nil
default:
return nil, fmt.Errorf("unsupported protocol: %s", protocol)
}
Expand Down
93 changes: 93 additions & 0 deletions internal/checkers/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package checkers

import (
"context"
"fmt"
"net"
"os"
"path/filepath"
"time"

"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)

type CMDChecker struct {
sshKeyPath string
}

func NewCMDChecker() *CMDChecker {
return &CMDChecker{
sshKeyPath: os.Getenv("SSH_KEY_PATH"),
}
}

func (c *CMDChecker) Protocol() Protocol {
return CMD
}

func (c *CMDChecker) Check(ctx context.Context, address string) CheckResult {
start := time.Now()

config, err := c.getSSHConfig()
if err != nil {
return newFailedResult(time.Since(start), fmt.Errorf("ssh config error: %w", err))
}

client, err := ssh.Dial("tcp", address, config)
if err != nil {
return newFailedResult(time.Since(start), fmt.Errorf("ssh dial error: %w", err))
}
defer client.Close()

session, err := client.NewSession()
if err != nil {
return newFailedResult(time.Since(start), fmt.Errorf("session error: %w", err))
}
defer session.Close()

if err := session.Run(ctx.Value("cmd").(string)); err != nil {
return newFailedResult(time.Since(start), fmt.Errorf("command error: %w", err))
}

return newSuccessResult(time.Since(start))
}

func (c *CMDChecker) getSSHConfig() (*ssh.ClientConfig, error) {
var auth []ssh.AuthMethod

if c.sshKeyPath != "" {
key, err := os.ReadFile(filepath.Clean(c.sshKeyPath))
if err != nil {
return nil, fmt.Errorf("unable to read SSH key: %w", err)
}

signer, err := ssh.ParsePrivateKey(key)
if err != nil {
return nil, fmt.Errorf("unable to parse SSH key: %w", err)
}
auth = append(auth, ssh.PublicKeys(signer))
} else {
socket := os.Getenv("SSH_AUTH_SOCK")
if socket != "" {
conn, err := net.Dial("unix", socket)
if err != nil {
return nil, fmt.Errorf("failed to connect to SSH agent: %w", err)
}

agentClient := agent.NewClient(conn)
auth = append(auth, ssh.PublicKeysCallback(agentClient.Signers))
}
}

if len(auth) == 0 {
return nil, fmt.Errorf("no SSH authentication methods available")
}

return &ssh.ClientConfig{
User: os.Getenv("SSH_USER"),
Auth: auth,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),

Check failure

Code scanning / CodeQL

Use of insecure HostKeyCallback implementation High

Configuring SSH ClientConfig with insecure HostKeyCallback implementation from
this source
.
Timeout: 10 * time.Second,
}, nil
}
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type CheckConfig struct {
Tags []string `yaml:"tags"`
RuleMode RuleMode `yaml:"ruleMode,omitempty"`
VerifyCert bool `yaml:"verifyCert,omitempty"`
Command string `yaml:"command,omitempty"`
}

type NotificationConfig struct {
Expand Down

0 comments on commit c880392

Please sign in to comment.