Skip to content

Commit

Permalink
feat(install): validate user API key format (#1510)
Browse files Browse the repository at this point in the history
  • Loading branch information
sanderblue authored Sep 7, 2023
1 parent ae3b1e8 commit c2c9bbf
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 9 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ require (
github.com/valyala/fastjson v1.6.3 // indirect
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand Down Expand Up @@ -264,6 +266,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
Expand Down
22 changes: 18 additions & 4 deletions internal/install/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/newrelic/newrelic-cli/internal/install/types"
"github.com/newrelic/newrelic-cli/internal/utils"
nrErrors "github.com/newrelic/newrelic-client-go/v2/pkg/errors"
nrRegion "github.com/newrelic/newrelic-client-go/v2/pkg/region"
)

var (
Expand Down Expand Up @@ -46,9 +47,10 @@ var Command = &cobra.Command{

sg := initSegment()
sg.Track(types.EventTypes.InstallStarted)
detailErr := validateProfile(config.DefaultMaxTimeoutSeconds, sg)

detailErr := validateProfile(config.DefaultMaxTimeoutSeconds, sg)
if detailErr != nil {
sg.Track(detailErr.EventName)
log.Fatal(detailErr)
}

Expand Down Expand Up @@ -126,19 +128,31 @@ func validateProfile(maxTimeoutSeconds int, sg *segment.Segment) *types.DetailEr

if accountID == 0 {
errorOccured = true
detailErr = types.NewDetailError(types.EventTypes.AccountIDMissing, "account ID is required")
detailErr = types.NewDetailError(types.EventTypes.AccountIDMissing, "Account ID is required.")
return detailErr
}

if APIKey == "" {
errorOccured = true
detailErr = types.NewDetailError(types.EventTypes.APIKeyMissing, "API key is required")
detailErr = types.NewDetailError(types.EventTypes.APIKeyMissing, "User API key is required.")
return detailErr
}

if !utils.IsValidUserAPIKeyFormat(APIKey) {
errorOccured = true
detailErr = types.NewDetailError(types.EventTypes.InvalidUserAPIKeyFormat, `Invalid user API key format detected. Please provide a valid user API key. User API keys usually have a prefix of "NRAK-" or "NRAA-".`)
return detailErr
}

if region == "" {
errorOccured = true
detailErr = types.NewDetailError(types.EventTypes.RegionMissing, "region is required")
detailErr = types.NewDetailError(types.EventTypes.RegionMissing, "Region is required.")
return detailErr
}

if _, err := nrRegion.Parse(region); err != nil {
errorOccured = true
detailErr = types.NewDetailError(types.EventTypes.InvalidRegion, `Invalid region provided. Valid regions are "US" or "EU".`)
return detailErr
}

Expand Down
16 changes: 13 additions & 3 deletions internal/install/command_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build unit

package install

import (
Expand All @@ -22,8 +24,7 @@ func TestInstallCommand(t *testing.T) {
testcobra.CheckCobraMetadata(t, Command)
testcobra.CheckCobraRequiredFlags(t, Command, []string{})
}
func TestCommandValiProfile(t *testing.T) {

func TestCommandValidProfile(t *testing.T) {
accountID := os.Getenv("NEW_RELIC_ACCOUNT_ID")
apiKey := os.Getenv("NEW_RELIC_API_KEY")
region := os.Getenv("NEW_RELIC_REGION")
Expand All @@ -50,12 +51,21 @@ func TestCommandValiProfile(t *testing.T) {
os.Setenv("NEW_RELIC_API_KEY", "")
err = validateProfile(5, c)
assert.Equal(t, types.EventTypes.APIKeyMissing, err.EventName)

os.Setenv("NEW_RELIC_API_KEY", "67890")
err = validateProfile(5, c)
assert.Equal(t, types.EventTypes.InvalidUserAPIKeyFormat, err.EventName)

if apiKey == "" {
os.Setenv("NEW_RELIC_API_KEY", "67890")
os.Setenv("NEW_RELIC_API_KEY", "NRAK-67890")
} else {
os.Setenv("NEW_RELIC_API_KEY", apiKey)
}

os.Setenv("NEW_RELIC_REGION", "au")
err = validateProfile(5, c)
assert.Equal(t, types.EventTypes.InvalidRegion, err.EventName)

os.Setenv("NEW_RELIC_REGION", "")
err = validateProfile(5, c)
assert.Equal(t, types.EventTypes.RegionMissing, err.EventName)
Expand Down
9 changes: 9 additions & 0 deletions internal/install/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ var EventTypes = struct {
NrIntegrationPollingErrror EventType
OtherError EventType
UnableToLocatePostedData EventType
InvalidUserAPIKeyFormat EventType
InvalidRegion EventType
}{
InstallStarted: "InstallStarted",
AccountIDMissing: "AccountIDMissing",
Expand All @@ -54,6 +56,8 @@ var EventTypes = struct {
UnableToDiscover: "UnableToDiscover",
NrIntegrationPollingErrror: "NrIntegrationPollingErrror",
OtherError: "OtherError",
InvalidUserAPIKeyFormat: "InvalidUserAPIKeyFormat",
InvalidRegion: "InvalidRegion",
}

func TryParseEventType(e string) (EventType, bool) {
Expand Down Expand Up @@ -84,7 +88,12 @@ func TryParseEventType(e string) (EventType, bool) {
return EventTypes.UnableToDiscover, true
case "NrIntegrationPollingErrror":
return EventTypes.NrIntegrationPollingErrror, true
case "InvalidUserAPIKeyFormat":
return EventTypes.InvalidUserAPIKeyFormat, true
case "InvalidRegion":
return EventTypes.InvalidRegion, true
}

return "", false
}

Expand Down
21 changes: 20 additions & 1 deletion internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import (
"os"
"os/signal"
"reflect"
"regexp"
"strconv"
"strings"
"syscall"
"time"

"github.com/mitchellh/go-homedir"

log "github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
)

var (
Expand Down Expand Up @@ -185,3 +186,21 @@ func IsExitStatusCode(exitCode int, err error) bool {
exitCodeString := fmt.Sprintf("exit status %d", exitCode)
return strings.Contains(err.Error(), exitCodeString)
}

func IsValidUserAPIKeyFormat(key string) bool {
validUserAPIKeyPrefixes := []string{"NRAK", "NRAA"}
prefix, keyString, found := strings.Cut(key, "-")

// If no hyphen was found, we don't have a user API key.
if !found {
return false
}

if !slices.Contains(validUserAPIKeyPrefixes, prefix) {
return false
}

var isAlphanumeric = regexp.MustCompile("^[a-zA-Z0-9_]*$").MatchString(keyString)

return isAlphanumeric
}
21 changes: 21 additions & 0 deletions internal/utils/utils_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build unit

package utils

import (
Expand Down Expand Up @@ -102,3 +104,22 @@ func TestLogIfFatal(t *testing.T) {
require.Equal(t, c.expectFatal, fatal)
}
}

func TestIsValidUserAPIKeyFormat_Valid(t *testing.T) {
result := IsValidUserAPIKeyFormat("NRAK-ABCDEBFGJIJKLMNOPQRSTUVWXYZ")
assert.True(t, result)
}

func TestIsValidUserAPIKeyFormat_Invalid(t *testing.T) {
// Invalid prefix
result := IsValidUserAPIKeyFormat("NRBR-ABCDEBFGJIJKLMNOPQRSTUVWXYZ")
assert.False(t, result)

// A license key is not a valid User API key (note, this is not a real license key)
result = IsValidUserAPIKeyFormat("4321abcsksd344ndlsdm20231mwd21230md12cbsdhk2")
assert.False(t, result)

// Special characters are invaid after the prefix hyphen
result = IsValidUserAPIKeyFormat("NRAK-@$%^!")
assert.False(t, result)
}

0 comments on commit c2c9bbf

Please sign in to comment.