Skip to content

Commit

Permalink
feat: Implement AnyAuth Proxy Authentication support
Browse files Browse the repository at this point in the history
* includes refactorings for improved testability

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: add go get to download gomock

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: check in generated mock

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: added additional test cases

refactored common test code

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: add simple minimal test for GetToken()

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: added comment

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: improve proxy tests

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: introduce case insensitive mechanisms and …

… support for AnyAuth, which selects out of multiple mechanisms proposed by the proxy

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: updating cache file which expired …

… will look into a more permanent solution later

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: minor improvements

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: fix and add tests

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: adapt/fix test

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: adapt to handle tcp reconnect on anyauth

Any initial connection without authorization values will be closed by the proxy and a new connection has to be established

* some minor refactorings and logging improvements

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: improve docker checks in acceptance tests

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: add ntlm and basic fake auth to proxy config

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: simplify proxy test

* includes renaming of IsSupportMechanism()

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: move mock server out of specific test …

… for better re-use

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: move some things into httpauth to enable …

… creating a module from it

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: use docker based proxy if possible …

… to avoid expired checked in files

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: fix and improve test

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: increase timeout and add log message

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: attempt to make test run in CI

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: attempt to make test run in CI

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: attempt to make test run in CI

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: attempt to make test run in CI

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: apply prettier

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

test: added unit test for AuthenticationHandler

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>

chore: run go mod tidy

Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>
Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>
Signed-off-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com>
  • Loading branch information
PeterSchafer committed Aug 2, 2022
1 parent a05a766 commit 467b621
Show file tree
Hide file tree
Showing 21 changed files with 1,480 additions and 258 deletions.
9 changes: 9 additions & 0 deletions cliv2/Makefile
Expand Up @@ -164,6 +164,15 @@ blackboxtest: build-blackboxtest
@echo "$(LOG_PREFIX) Running $@"
TEST_SNYK_EXECUTABLE_PATH=$(TEST_SNYK_EXECUTABLE_PATH) $(BUILD_DIR)/$(TEST_EXECUTABLE_NAME) -test.v

$(WORKING_DIR)/internal/httpauth/generated/httpauth_generated_mock.go:
@$(GOCMD) generate ./internal/httpauth/

$(WORKING_DIR)/internal/httpauth/generated/spnego_generated_mock.go:
@$(GOCMD) generate ./internal/httpauth/

.PHONY: generate
generate: $(WORKING_DIR)/internal/httpauth/generated/httpauth_generated_mock.go $(WORKING_DIR)/internal/httpauth/generated/spnego_generated_mock.go

.PHONY: whiteboxtest
whiteboxtest:
@echo "$(LOG_PREFIX) Running $@"
Expand Down
8 changes: 4 additions & 4 deletions cliv2/cmd/cliv2/main.go
Expand Up @@ -39,20 +39,20 @@ func GetConfiguration(args []string) (EnvironmentVariables, []string) {

envVariables := EnvironmentVariables{
CacheDirectory: os.Getenv("SNYK_CACHE_PATH"),
ProxyAuthenticationMechanism: httpauth.NoAuth,
ProxyAuthenticationMechanism: httpauth.AnyAuth,
Insecure: false,
}

if utils.Contains(args, "--proxy-negotiate") {
envVariables.ProxyAuthenticationMechanism = httpauth.Negotiate
if utils.Contains(args, "--proxy-noauth") {
envVariables.ProxyAuthenticationMechanism = httpauth.NoAuth
}

envVariables.Insecure = utils.Contains(args, "--insecure")

envVariables.ProxyAddr, _ = argsAsMap["--proxy"]

// filter args not meant to be forwarded to CLIv1 or an Extensions
elementsToFilter := []string{"--proxy=", "--proxy-negotiate"}
elementsToFilter := []string{"--proxy=", "--proxy-noauth"}
filteredArgs := args
for _, element := range elementsToFilter {
filteredArgs = utils.RemoveSimilar(filteredArgs, element)
Expand Down
21 changes: 19 additions & 2 deletions cliv2/cmd/cliv2/main_test.go
Expand Up @@ -36,12 +36,29 @@ func Test_MainWithErrorCode_no_cache(t *testing.T) {
}

func Test_GetConfiguration(t *testing.T) {
cmd := "_bin/snyk_darwin_arm64 --debug --proxy-negotiate --proxy=http://host.example.com:3128 --insecure test"
cmd := "_bin/snyk_darwin_arm64 --debug --proxy=http://host.example.com:3128 --insecure test"
args := strings.Split(cmd, " ")

expectedConfig := main.EnvironmentVariables{
Insecure: true,
ProxyAuthenticationMechanism: httpauth.Negotiate,
ProxyAuthenticationMechanism: httpauth.AnyAuth,
ProxyAddr: "http://host.example.com:3128",
}
expectedArgs := []string{"_bin/snyk_darwin_arm64", "--debug", "--insecure", "test"}

actualConfig, actualArgs := main.GetConfiguration(args)

assert.Equal(t, expectedArgs, actualArgs)
assert.Equal(t, expectedConfig, actualConfig)
}

func Test_GetConfiguration02(t *testing.T) {
cmd := "_bin/snyk_darwin_arm64 --debug --proxy-noauth --proxy=http://host.example.com:3128 --insecure test"
args := strings.Split(cmd, " ")

expectedConfig := main.EnvironmentVariables{
Insecure: true,
ProxyAuthenticationMechanism: httpauth.NoAuth,
ProxyAddr: "http://host.example.com:3128",
}
expectedArgs := []string{"_bin/snyk_darwin_arm64", "--debug", "--insecure", "test"}
Expand Down
1 change: 1 addition & 0 deletions cliv2/go.mod
Expand Up @@ -5,6 +5,7 @@ go 1.18
require (
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e
github.com/golang/mock v1.6.0
github.com/jcmturner/gokrb5/v8 v8.4.2
github.com/stretchr/testify v1.7.0
golang.org/x/net v0.0.0-20220630215102-69896b714898
Expand Down
20 changes: 20 additions & 0 deletions cliv2/go.sum
Expand Up @@ -6,6 +6,8 @@ github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e h1:99KFda6F/mw8xSf
github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
Expand All @@ -32,18 +34,36 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
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-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
112 changes: 81 additions & 31 deletions cliv2/internal/httpauth/httpauth.go
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"log"
"net/url"
"strings"
)

type AuthenticationMechanism string
Expand All @@ -13,10 +14,10 @@ type AuthenticationState int
const maxCycleCount int = 10

const (
NoAuth AuthenticationMechanism = "NoAuth"
Mock AuthenticationMechanism = "Mock"
Negotiate AuthenticationMechanism = "Negotiate"
UnknownMechanism AuthenticationMechanism = "UnknownMechanism"
NoAuth AuthenticationMechanism = "noauth"
Negotiate AuthenticationMechanism = "negotiate"
AnyAuth AuthenticationMechanism = "anyauth"
UnknownMechanism AuthenticationMechanism = "unknownmechanism"
)

const (
Expand All @@ -40,24 +41,29 @@ type AuthenticationHandlerInterface interface {
Succesful()
IsStopped() bool
GetAuthorizationValue(url *url.URL, responseToken string) (string, error)
Update(availableMechanism map[AuthenticationMechanism]string) (string, error)
SetLogger(logger *log.Logger)
SetSpnegoProvider(spnegoProvider SpnegoProvider)
}

type AuthenticationHandler struct {
spnegoProvider SpnegoProvider
Mechanism AuthenticationMechanism
state AuthenticationState
cycleCount int
logger *log.Logger
spnegoProvider SpnegoProvider
Mechanism AuthenticationMechanism
activeMechanism AuthenticationMechanism
state AuthenticationState
cycleCount int
logger *log.Logger
}

func NewHandler(mechanism AuthenticationMechanism) AuthenticationHandlerInterface {
a := &AuthenticationHandler{
spnegoProvider: SpnegoProviderInstance(),
Mechanism: mechanism,
state: Initial,
logger: log.New(io.Discard, "", 0),
spnegoProvider: SpnegoProviderInstance(),
Mechanism: mechanism,
activeMechanism: mechanism,
state: Initial,
logger: log.New(io.Discard, "", 0),
}

return a
}

Expand All @@ -66,12 +72,10 @@ func (a *AuthenticationHandler) Close() {
a.state = Close
}

func (a *AuthenticationHandler) GetAuthorizationValue(url *url.URL, responseToken string) (string, error) {
authorizeValue := ""
mechanism := string(a.Mechanism)
var err error
func (a *AuthenticationHandler) GetAuthorizationValue(url *url.URL, responseToken string) (authorizeValue string, err error) {
mechanism := StringFromAuthenticationMechanism(a.activeMechanism)

if a.Mechanism == Negotiate { // supporting mechanism: Negotiate (SPNEGO)
if a.activeMechanism == Negotiate { // supporting mechanism: Negotiate (SPNEGO)
var token string
var done bool

Expand All @@ -89,31 +93,49 @@ func (a *AuthenticationHandler) GetAuthorizationValue(url *url.URL, responseToke
}

if done {
a.logger.Println("Security context done!")
a.logger.Println("Local security context established!")
}

authorizeValue = mechanism + " " + token
} else if a.Mechanism == Mock { // supporting mechanism: Mock for testing
authorizeValue = mechanism + " " + responseToken
a.Succesful()

if len(token) > 0 {
mechanisms, _ := GetMechanismsFromHttpFieldValue(authorizeValue)
a.logger.Printf("Authorization to %s using: %s", url, mechanisms)
}
}

a.cycleCount++
if a.cycleCount >= maxCycleCount {
err = fmt.Errorf("Failed to authenticate with %d cycles, stopping now!", maxCycleCount)
err = fmt.Errorf("Failed to authenticate within %d cycles, stopping now!", maxCycleCount)
authorizeValue = ""
}

return authorizeValue, err
}

func (a *AuthenticationHandler) IsStopped() bool {
return (a.state == Done || a.state == Error || a.state == Cancel || a.state == Close)
func (a *AuthenticationHandler) Update(availableMechanism map[AuthenticationMechanism]string) (responseToken string, err error) {

// if AnyAuth is selected, we need to determine the best supported mechanism on both sides
if a.activeMechanism == AnyAuth {
// currently we only support Negotiate, AnyAuth will use Negotiate if the communication partner proposes it
if _, ok := availableMechanism[Negotiate]; ok {
a.activeMechanism = Negotiate
a.logger.Printf("Selected Mechanism: %s\n", StringFromAuthenticationMechanism(a.activeMechanism))
}
}

// extract the token for the active mechanism
if token, ok := availableMechanism[a.activeMechanism]; ok {
responseToken = token
} else {
err = fmt.Errorf("Incorrect or unsupported Mechanism detected! %s", availableMechanism)
}

return responseToken, err
}

func (a *AuthenticationHandler) Reset() {
a.state = Initial
a.cycleCount = 0
a.logger.Println("AuthenticationHandler.Reset()")
func (a *AuthenticationHandler) IsStopped() bool {
return (a.state == Done || a.state == Error || a.state == Cancel || a.state == Close)
}

func (a *AuthenticationHandler) Cancel() {
Expand All @@ -131,10 +153,38 @@ func (a *AuthenticationHandler) SetLogger(logger *log.Logger) {
a.spnegoProvider.SetLogger(logger)
}

func (a *AuthenticationHandler) SetSpnegoProvider(spnegoProvider SpnegoProvider) {
a.spnegoProvider = spnegoProvider
}

func StringFromAuthenticationMechanism(mechanism AuthenticationMechanism) string {
return string(mechanism)
return strings.Title(string(mechanism))
}

func AuthenticationMechanismFromString(mechanism string) AuthenticationMechanism {
return AuthenticationMechanism(mechanism)
tmp := strings.ToLower(mechanism)
return AuthenticationMechanism(tmp)
}

func GetMechanismAndToken(HttpFieldValue string) (AuthenticationMechanism, string) {
mechanism := UnknownMechanism
token := ""

authenticateValue := strings.Split(HttpFieldValue, " ")
if len(authenticateValue) >= 1 {
mechanism = AuthenticationMechanismFromString(authenticateValue[0])
}

if len(authenticateValue) == 2 {
token = authenticateValue[1]
}

return mechanism, token
}

func IsSupportedMechanism(mechanism AuthenticationMechanism) bool {
if mechanism == Negotiate || mechanism == AnyAuth {
return true
}
return false
}

0 comments on commit 467b621

Please sign in to comment.