Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Phchen/updatesecutirypatch #573

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2866cd2
gRPC configuration struct and example
Jan 20, 2023
af50aae
GRPC health check client
Jan 20, 2023
87a448c
handlers for grpc requests
Jan 21, 2023
0b6679d
grpc url handling issue fixed
Jan 21, 2023
025d50a
Merge branch 'grpc'
Jan 21, 2023
b48f4b8
refactored so grpc components are structured the same as existing com…
Jan 22, 2023
38eec60
refactored so grpc components are structured the same as existing com…
Jan 22, 2023
82b199b
grpc.go is not needed
Jan 22, 2023
3bd9304
minor grpc client update
Jan 22, 2023
82e49d6
CertificateExpiration handling added for condition check
Jan 22, 2023
f9b1dd3
more refactoring grpc client handling to use the shared conn
Jan 23, 2023
0c4ad87
now header value can have a load() function
Jan 23, 2023
dffb604
grpc url format update
Jan 23, 2023
a54ad44
cleanup comment
Jan 24, 2023
4e9ddd7
clean up unnecessary check
Jan 24, 2023
ed15897
now load() has a capability of caching and reload if a file is modifi…
Jan 25, 2023
08e6f48
To enable build an executable binary for linux on the local osx devbox
Aug 17, 2023
fa641ba
fix(misc): security patch
icloudphil-exp Sep 8, 2023
0c18dfd
Merge pull request #1 from baechul/updateDependency
baechul Sep 11, 2023
ff93756
fix(misc): update the patched version
icloudphil-exp Sep 22, 2023
2a19b0d
Merge pull request #2 from baechul/phchen/updatesecutirypatch
icloudphil Sep 22, 2023
bcb9852
address vmop issue
icloudphil-exp Sep 30, 2023
9a29cfd
Merge pull request #3 from baechul/phchen/updatesecutirypatch
icloudphil Sep 30, 2023
bbac4e9
fix(misc): update VMOP fix for 10.4 scan
icloudphil-exp Oct 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Build the go application into a binary
FROM golang:alpine as builder
FROM --platform=linux/amd64 golang:alpine as builder
RUN apk --update add ca-certificates
WORKDIR /app
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o gatus .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o gatus .

# Run Tests inside docker image if you don't have a configured go environment
#RUN apk update && apk add --virtual build-dependencies build-base gcc
#RUN go test ./... -mod vendor

# Run the binary on an empty container
FROM scratch
FROM --platform=linux/amd64 scratch
COPY --from=builder /app/gatus .
COPY --from=builder /app/config.yaml ./config/config.yaml
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
Expand Down
16 changes: 15 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net"
"net/http"
"net/smtp"
"net/url"
"runtime"
"strings"
"time"
Expand All @@ -16,6 +17,8 @@ import (
"github.com/TwiN/whois"
"github.com/go-ping/ping"
"github.com/ishidawataru/sctp"
"google.golang.org/grpc"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

var (
Expand All @@ -37,6 +40,17 @@ func GetHTTPClient(config *Config) *http.Client {
return config.getHTTPClient()
}

// GetGRPCHealthClient returns a GRPC health client for the given GRPC url
func GetGRPCHealthClient(config *Config, urlStr string) (healthpb.HealthClient, *grpc.ClientConn, error) {
urlObject, _ := url.Parse(urlStr)
hostPort := urlObject.Hostname()+":"+urlObject.Port()

if config == nil {
return defaultGrpcConfig.getGRPCHealthClient(hostPort)
}
return config.getGRPCHealthClient(hostPort)
}

// GetDomainExpiration retrieves the duration until the domain provided expires
func GetDomainExpiration(hostname string) (domainExpiration time.Duration, err error) {
var retrievedCachedValue bool
Expand Down Expand Up @@ -184,7 +198,7 @@ func Ping(address string, config *Config) (bool, time.Duration) {
return true, 0
}

// InjectHTTPClient is used to inject a custom HTTP client for testing purposes
// InjecthttpClient is used to inject a custom HTTP client for testing purposes
func InjectHTTPClient(httpClient *http.Client) {
injectedHTTPClient = httpClient
}
95 changes: 93 additions & 2 deletions client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,65 @@ package client
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"regexp"
"strconv"
"time"

"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
)

const (
defaultHTTPTimeout = 10 * time.Second
defaultGRPCTimeout = 10 * time.Second
)

var (
ErrInvalidDNSResolver = errors.New("invalid DNS resolver specified. Required format is {proto}://{ip}:{port}")
ErrInvalidDNSResolverPort = errors.New("invalid DNS resolver port")
ErrInvalidClientOAuth2Config = errors.New("invalid oauth2 configuration: must define all fields for client credentials flow (token-url, client-id, client-secret, scopes)")

ErrFileNotExist = errors.New("file not exists: ")
ErrFailedToLoadCert = errors.New("failed to load a server certificate in the configuration")
ErrFailedToParseCert = errors.New("Failed to parse the server certificate")
ErrFailedToCreateConnection = errors.New("Failed to create a client connection")

// default client configuration for non-grpc
defaultConfig = Config{
Insecure: false,
IgnoreRedirect: false,
Timeout: defaultHTTPTimeout,
}
// default client configuration for grpc. gRPC client does not handle 302 redirects.
// a gRPC client is not meant to be a full HTTP client. For example, cookies and redirects are not part of gRPC.
defaultGrpcConfig = Config {
Insecure: false,
Timeout: defaultGRPCTimeout,
}
)

// GetDefaultConfig returns a copy of the default configuration
// GetDefaultConfig returns a copy of the default client configuration for non-grpc.
func GetDefaultConfig() *Config {
cfg := defaultConfig
return &cfg
}

// GetGrpcDefaultConfig returns a copy of the default client configuration for grpc.
func GetGrpcDefaultConfig() *Config {
cfg := defaultGrpcConfig
return &cfg
}

// Config is the configuration for clients
type Config struct {
// Insecure determines whether to skip verifying the server's certificate chain and host name
Expand All @@ -58,7 +83,16 @@ type Config struct {
// See configureOAuth2 for more details.
OAuth2Config *OAuth2Config `yaml:"oauth2,omitempty"`

// Cert is a file path where a server certifcate is at when the client connection requires the certificate.
// If CertPath is passed, the file will be loaded into the cert.
CertPath string `yaml:"certpath,omitempty"`

// text representation of the server certificate. If CertPath is passed, the file will be loaded into the cert.
cert string

httpClient *http.Client
grpcClientConn *grpc.ClientConn
grpcHealthClient healthpb.HealthClient
}

// DNSResolverConfig is the parsed configuration from the DNSResolver config string.
Expand Down Expand Up @@ -90,6 +124,19 @@ func (c *Config) ValidateAndSetDefaults() error {
if c.HasOAuth2Config() && !c.OAuth2Config.isValid() {
return ErrInvalidClientOAuth2Config
}
if len(c.CertPath) != 0 {
_, err := os.Stat(c.CertPath)
if os.IsNotExist(err) {
return fmt.Errorf("%v: %s", ErrFileNotExist, c.CertPath)
}

// load a server certificate from the local disk
serverCA, err := os.ReadFile(c.CertPath)
if err != nil {
return fmt.Errorf("%v: %w", ErrFailedToLoadCert, err)
}
c.cert = string(serverCA)
}
return nil
}

Expand Down Expand Up @@ -197,3 +244,47 @@ func configureOAuth2(httpClient *http.Client, c OAuth2Config) *http.Client {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
return oauth2cfg.Client(ctx)
}

// Note that unlike a http client, client.dns-resolver and client.oauth2 are not supported for the grpc health check client.
func (c *Config) getGRPCHealthClient(hostPort string) (healthpb.HealthClient, *grpc.ClientConn, error) {
if c.grpcHealthClient == nil || c.grpcClientConn == nil || c.isGrpcClientConnShtdown() {
// initial tls configuration
tlsConfig := &tls.Config {
InsecureSkipVerify: c.Insecure,
}

// handle the server certificate if given
if len(c.cert) != 0 {
// parse and append a server certificate
serverCA := []byte(c.cert)
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(serverCA) {
return nil, nil, fmt.Errorf("%v: %v", ErrFailedToParseCert, c.cert)
}

// update the tls configuration with the server cert.
tlsConfig.RootCAs = certPool
}

// create the connection
// (TODO) currently mTLS is not supported. If a gRPC server requires a mTLS for health check,
// it would be ideal to request to remove mTLS for the health check.
creds := credentials.NewTLS(tlsConfig)
conn, err := grpc.Dial(hostPort, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, nil, fmt.Errorf("%v: %w", ErrFailedToCreateConnection, err)
}
c.grpcClientConn = conn
c.grpcHealthClient = healthpb.NewHealthClient(conn)
}
return c.grpcHealthClient, c.grpcClientConn, nil
}

func (c *Config) isGrpcClientConnShtdown() bool {
if c.grpcClientConn == nil {
return true
}

connState :=c.grpcClientConn.GetState()
return connState == connectivity.Shutdown
}
13 changes: 13 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,16 @@ endpoints:
interval: 1h
conditions:
- "[DOMAIN_EXPIRATION] > 720h"

- name: grpc-service
url: "grpc://grpc-service.example.com:443"
interval: 1m
client:
insecure: true
timeout: 3s
headers:
Authorization: "Bearer =load(~/.identity-jwt)"
conditions:
- "[BODY].status == SERVING"
- "[RESPONSE_TIME] < 3000"

Loading