Skip to content

Commit

Permalink
feat: OBS-489 - allow change tls verification by setting env variable (
Browse files Browse the repository at this point in the history
…#127)

* feat: OBS-489 - allow change tls verification by setting env variable

* feat: OBS-489 - add unit tests
  • Loading branch information
leoparente authored Jul 22, 2024
1 parent 65c30e1 commit b855cc7
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 10 deletions.
1 change: 0 additions & 1 deletion diode-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ curl -o .env https://raw.githubusercontent.com/netboxlabs/diode/develop/diode-se

Edit the `.env` to match your environment:
* `NETBOX_DIODE_PLUGIN_API_BASE_URL`: URL for the Diode NetBox plugin API
* `NETBOX_API_URL`: URL for your NetBox
* `DIODE_TO_NETBOX_API_KEY`: API key generated with the Diode NetBox plugin installation
* `INGESTION_API_KEY`: API key generated with the Diode NetBox plugin installation
* `NETBOX_TO_DIODE_API_KEY`: API key generated with the Diode NetBox plugin installation
Expand Down
1 change: 0 additions & 1 deletion diode-server/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ services:
- DIODE_TO_NETBOX_API_KEY=${DIODE_TO_NETBOX_API_KEY}
- NETBOX_TO_DIODE_API_KEY=${NETBOX_TO_DIODE_API_KEY}
- INGESTION_API_KEY=${INGESTION_API_KEY}
- NETBOX_API_URL=${NETBOX_API_URL}
- LOGGING_LEVEL=${LOGGING_LEVEL}
- SENTRY_DSN=${SENTRY_DSN}
restart: always
Expand Down
1 change: 0 additions & 1 deletion diode-server/docker/sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ NETBOX_DIODE_PLUGIN_API_BASE_URL=http://NETBOX_HOST/api/plugins/diode
DIODE_TO_NETBOX_API_KEY=1368dbad13e418d5a443d93cf255edde03a2a754
NETBOX_TO_DIODE_API_KEY=1e99338b8cab5fc637bc55f390bda1446f619c42
INGESTION_API_KEY=5a52c45ee8231156cb620d193b0291912dd15433
NETBOX_API_URL=http://NETBOX_HOST/api
LOGGING_LEVEL=DEBUG
SENTRY_DSN=
40 changes: 39 additions & 1 deletion diode-server/netboxdiodeplugin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package netboxdiodeplugin
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"net/url"
"os"
Expand All @@ -30,6 +32,9 @@ const (
// BaseURLEnvVarName is the environment variable name for the NetBox Diode plugin HTTP base URL
BaseURLEnvVarName = "NETBOX_DIODE_PLUGIN_API_BASE_URL"

// TLSSkipVerifyEnvVarName is the environment variable name for Netbox Diode plugin TLS verification
TLSSkipVerifyEnvVarName = "NETBOX_DIODE_PLUGIN_SKIP_TLS_VERIFY"

// TimeoutSecondsEnvVarName is the environment variable name for the NetBox Diode plugin HTTP timeout
TimeoutSecondsEnvVarName = "NETBOX_DIODE_PLUGIN_API_TIMEOUT_SECONDS"

Expand Down Expand Up @@ -94,9 +99,30 @@ type Client struct {
baseURL *url.URL
}

// NewHTTPTransport creates a http Transport Layer
func NewHTTPTransport() *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: skipTLS(),
},
}
}

// NewClient creates a new NetBox Diode plugin client
func NewClient(logger *slog.Logger, apiKey string) (*Client, error) {
rt, err := newAPIRoundTripper(apiKey, http.DefaultTransport)
transport := NewHTTPTransport()

rt, err := newAPIRoundTripper(apiKey, transport)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -137,6 +163,18 @@ func baseURL() string {
return u
}

func skipTLS() bool {
skipTLS, ok := os.LookupEnv(TLSSkipVerifyEnvVarName)
if !ok {
return false
}
skip, err := strconv.ParseBool(skipTLS)
if err != nil {
return false
}
return skip
}

func httpTimeout() (time.Duration, error) {
timeoutSecondsStr, ok := os.LookupEnv(TimeoutSecondsEnvVarName)
if !ok || len(timeoutSecondsStr) == 0 {
Expand Down
76 changes: 71 additions & 5 deletions diode-server/netboxdiodeplugin/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,34 @@ import (
"github.com/netboxlabs/diode/diode-server/netboxdiodeplugin"
)

func TestTransportSecurity(t *testing.T) {
tests := []struct {
name string
expectedInsecure bool
}{
{
name: "enable insecure mode",
expectedInsecure: true,
},
{
name: "default secure TLS config",
expectedInsecure: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cleanUpEnvVars()

if tt.expectedInsecure {
_ = os.Setenv(netboxdiodeplugin.TLSSkipVerifyEnvVarName, "true")
}

httpTransport := netboxdiodeplugin.NewHTTPTransport()
assert.Equal(t, tt.expectedInsecure, httpTransport.TLSClientConfig.InsecureSkipVerify)
})
}
}

func TestNewClient(t *testing.T) {
tests := []struct {
name string
Expand All @@ -25,6 +53,7 @@ func TestNewClient(t *testing.T) {
timeout string
setBaseURLEnvVar bool
setTimeoutEnvVar bool
setTLSSkipEnvVar bool
shouldError bool
}{
{
Expand All @@ -34,6 +63,7 @@ func TestNewClient(t *testing.T) {
timeout: "5",
setBaseURLEnvVar: true,
setTimeoutEnvVar: true,
setTLSSkipEnvVar: false,
shouldError: false,
},
{
Expand All @@ -52,6 +82,7 @@ func TestNewClient(t *testing.T) {
timeout: "5",
setBaseURLEnvVar: true,
setTimeoutEnvVar: true,
setTLSSkipEnvVar: false,
shouldError: true,
},
{
Expand All @@ -61,6 +92,7 @@ func TestNewClient(t *testing.T) {
timeout: "",
setBaseURLEnvVar: true,
setTimeoutEnvVar: false,
setTLSSkipEnvVar: false,
shouldError: false,
},
{
Expand All @@ -70,6 +102,7 @@ func TestNewClient(t *testing.T) {
timeout: "-1",
setBaseURLEnvVar: true,
setTimeoutEnvVar: true,
setTLSSkipEnvVar: false,
shouldError: true,
},
{
Expand All @@ -79,8 +112,19 @@ func TestNewClient(t *testing.T) {
timeout: "5",
setBaseURLEnvVar: true,
setTimeoutEnvVar: true,
setTLSSkipEnvVar: false,
shouldError: true,
},
{
name: "set TLS skip verify",
apiKey: "test",
baseURL: "",
timeout: "5",
setBaseURLEnvVar: false,
setTimeoutEnvVar: true,
setTLSSkipEnvVar: true,
shouldError: false,
},
}

logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: false}))
Expand All @@ -95,6 +139,9 @@ func TestNewClient(t *testing.T) {
if tt.setTimeoutEnvVar {
_ = os.Setenv(netboxdiodeplugin.TimeoutSecondsEnvVarName, tt.timeout)
}
if tt.setTLSSkipEnvVar {
_ = os.Setenv(netboxdiodeplugin.TLSSkipVerifyEnvVarName, "true")
}

client, err := netboxdiodeplugin.NewClient(logger, tt.apiKey)
if tt.shouldError {
Expand All @@ -115,6 +162,7 @@ func TestRetrieveObjectState(t *testing.T) {
apiKey string
mockServerResponse string
response any
tlsSkipVerify bool
shouldError bool
}{
{
Expand All @@ -132,7 +180,8 @@ func TestRetrieveObjectState(t *testing.T) {
},
},
},
shouldError: false,
tlsSkipVerify: true,
shouldError: false,
},
{
name: "valid response for DCIM site with query",
Expand All @@ -150,7 +199,8 @@ func TestRetrieveObjectState(t *testing.T) {
},
},
},
shouldError: false,
tlsSkipVerify: true,
shouldError: false,
},
{
name: "valid response for DCIM device with query and additional attributes",
Expand All @@ -173,7 +223,8 @@ func TestRetrieveObjectState(t *testing.T) {
},
},
},
shouldError: false,
tlsSkipVerify: true,
shouldError: false,
},
{
name: "response for invalid object - empty object",
Expand All @@ -187,13 +238,23 @@ func TestRetrieveObjectState(t *testing.T) {
Device: &netbox.DcimDevice{},
},
},
shouldError: false,
tlsSkipVerify: true,
shouldError: false,
},
{
name: "invalid server response",
params: netboxdiodeplugin.RetrieveObjectStateQueryParams{ObjectType: netbox.DcimDeviceObjectType, ObjectID: 1},
apiKey: "barfoo",
mockServerResponse: ``,
tlsSkipVerify: true,
shouldError: true,
},
{
name: "tls bad certificate",
params: netboxdiodeplugin.RetrieveObjectStateQueryParams{ObjectType: netbox.DcimDeviceObjectType, ObjectID: 1},
apiKey: "barfoo",
mockServerResponse: ``,
tlsSkipVerify: false,
shouldError: true,
},
}
Expand All @@ -220,12 +281,16 @@ func TestRetrieveObjectState(t *testing.T) {
assert.Equal(t, r.Header.Get("User-Agent"), fmt.Sprintf("%s/%s", netboxdiodeplugin.SDKName, netboxdiodeplugin.SDKVersion))
_, _ = w.Write([]byte(tt.mockServerResponse))
}

mux := http.NewServeMux()
mux.HandleFunc("/api/diode/object-state/", handler)
ts := httptest.NewServer(mux)
ts := httptest.NewTLSServer(mux)
defer ts.Close()

_ = os.Setenv(netboxdiodeplugin.BaseURLEnvVarName, fmt.Sprintf("%s/api/diode", ts.URL))
if tt.tlsSkipVerify {
_ = os.Setenv(netboxdiodeplugin.TLSSkipVerifyEnvVarName, "true")
}

client, err := netboxdiodeplugin.NewClient(logger, tt.apiKey)
require.NoError(t, err)
Expand Down Expand Up @@ -347,6 +412,7 @@ func TestApplyChangeSet(t *testing.T) {
func cleanUpEnvVars() {
_ = os.Unsetenv(netboxdiodeplugin.BaseURLEnvVarName)
_ = os.Unsetenv(netboxdiodeplugin.TimeoutSecondsEnvVarName)
_ = os.Unsetenv(netboxdiodeplugin.TLSSkipVerifyEnvVarName)
}

func ptrInt(i int) *int {
Expand Down
1 change: 0 additions & 1 deletion diode-server/reconciler/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ type Config struct {
RedisPassword string `envconfig:"REDIS_PASSWORD" required:"true"`
RedisDB int `envconfig:"REDIS_DB" default:"0"`
RedisStreamDB int `envconfig:"REDIS_STREAM_DB" default:"1"`
NetBoxAPIURL string `envconfig:"NETBOX_API_URL" required:"true"`

// API keys
DiodeToNetBoxAPIKey string `envconfig:"DIODE_TO_NETBOX_API_KEY" required:"true"`
Expand Down

0 comments on commit b855cc7

Please sign in to comment.