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

chore: Authentication negotiation for Kerberos #3430

Merged
merged 1 commit into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion cliv2/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ bindir = $(exec_prefix)/bin
WORKING_DIR = $(CURDIR)
BUILD_DIR = $(WORKING_DIR)/_bin
CACHE_DIR = $(WORKING_DIR)/_cache
SRCS = $(shell find $(WORKING_DIR) -type f -name '*.go')

# load cached variables if available
-include $(CACHE_DIR)/variables.mk
Expand Down Expand Up @@ -137,7 +138,7 @@ dependencies: $(V1_DIRECTORY)/$(V1_EXECUTABLE_NAME) $(V1_DIRECTORY)/$(V1_EXECUTA
.PHONY: configure
configure: $(V2_DIRECTORY)/cliv2.version $(CACHE_DIR) $(CACHE_DIR)/version.mk $(CACHE_DIR)/variables.mk $(V1_DIRECTORY)/$(V1_EMBEDDED_FILE_OUTPUT) dependencies

$(BUILD_DIR)/$(V2_EXECUTABLE_NAME): $(BUILD_DIR)
$(BUILD_DIR)/$(V2_EXECUTABLE_NAME): $(BUILD_DIR) $(SRCS)
@echo "$(LOG_PREFIX) Building ( $(BUILD_DIR)/$(V2_EXECUTABLE_NAME) )"
@GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOCMD) build -o $(BUILD_DIR)/$(V2_EXECUTABLE_NAME) $(WORKING_DIR)/cmd/cliv2/main.go

Expand Down
6 changes: 5 additions & 1 deletion cliv2/cmd/cliv2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ func getDebugLogger(args []string) *log.Logger {
debugLogger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)
debug := utils.Contains(args, "--debug")

if !debug {
debug = utils.Contains(args, "-d")
}

if !debug {
debugLogger.SetOutput(ioutil.Discard)
}
Expand Down Expand Up @@ -95,7 +99,7 @@ func MainWithErrorCode(envVariables EnvironmentVariables, args []string) int {
return cliv2.SNYK_EXIT_CODE_ERROR
}

wrapperProxy.SetUpstreamProxy(envVariables.ProxyAddr)
wrapperProxy.SetUpstreamProxyFromUrl(envVariables.ProxyAddr)
wrapperProxy.SetUpstreamProxyAuthentication(envVariables.ProxyAuthenticationMechanism)

port, err := wrapperProxy.Start()
Expand Down
8 changes: 4 additions & 4 deletions cliv2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ module github.com/snyk/cli/cliv2
go 1.18

require (
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e
github.com/jcmturner/gokrb5/v8 v8.4.2
github.com/stretchr/testify v1.7.0
golang.org/x/net v0.0.0-20220630215102-69896b714898
)

require (
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/dpotapov/go-spnego v0.0.0-20220426193508-b7f82e4507db // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.0.0 // indirect
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.2 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
14 changes: 6 additions & 8 deletions cliv2/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3Uu
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dpotapov/go-spnego v0.0.0-20220426193508-b7f82e4507db h1:3EIvol92cLWG5m13Me3/LfEtQqr23xzwZCxiWoT5gGE=
github.com/dpotapov/go-spnego v0.0.0-20220426193508-b7f82e4507db/go.mod h1:AVSs/gZKt1bOd2AhkhbS7Qh56Hv7klde22yXVbwYJhc=
github.com/elazarl/goproxy v0.0.0-20220328115640-894aeddb713e h1:99KFda6F/mw8xSfceY2JEVCrYWX7l+Ms6BcO5wEct+Q=
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/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=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
Expand All @@ -33,19 +33,17 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
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
140 changes: 106 additions & 34 deletions cliv2/internal/httpauth/httpauth.go
Original file line number Diff line number Diff line change
@@ -1,68 +1,140 @@
package httpauth

import (
"net/http"
"fmt"
"io"
"log"
"net/url"

"github.com/dpotapov/go-spnego"
)

type AuthenticationMechanism int
type AuthenticationMechanism string
type AuthenticationState int

const maxCycleCount int = 10

const (
NoAuth AuthenticationMechanism = "NoAuth"
Mock AuthenticationMechanism = "Mock"
Negotiate AuthenticationMechanism = "Negotiate"
UnknownMechanism AuthenticationMechanism = "UnknownMechanism"
)

const (
NoAuth AuthenticationMechanism = iota
Mock AuthenticationMechanism = iota
Negotiate AuthenticationMechanism = iota
Initial AuthenticationState = iota
Negotiating AuthenticationState = iota
Done AuthenticationState = iota
Error AuthenticationState = iota
Cancel AuthenticationState = iota
Close AuthenticationState = iota
)

const (
AuthorizationKey string = "Authorization"
ProxyAuthorizationKey string = "Proxy-Authorization"
ProxyAuthenticateKey string = "Proxy-Authenticate"
)

type AuthenticationHandlerInterface interface {
PeterSchafer marked this conversation as resolved.
Show resolved Hide resolved
Close()
Cancel()
Succesful()
IsStopped() bool
GetAuthorizationValue(url *url.URL, responseToken string) (string, error)
SetLogger(logger *log.Logger)
}

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

func (a *AuthenticationHandler) GetAuthorizationValue(url *url.URL) (string, error) {
func NewHandler(mechanism AuthenticationMechanism) AuthenticationHandlerInterface {
a := &AuthenticationHandler{
spnegoProvider: SpnegoProviderInstance(),
Mechanism: mechanism,
state: Initial,
logger: log.New(io.Discard, "", 0),
}
return a
}

var authorizeValue string
func (a *AuthenticationHandler) Close() {
a.spnegoProvider.Close()
a.state = Close
}

tmpRequest := http.Request{
URL: url,
Header: map[string][]string{},
}
func (a *AuthenticationHandler) GetAuthorizationValue(url *url.URL, responseToken string) (string, error) {
authorizeValue := ""
mechanism := string(a.Mechanism)
var err error

if a.Mechanism == Negotiate { // supporting mechanism: Negotiate (SPNEGO)
var provider spnego.Provider = spnego.New()
cannonicalize := false
var token string
var done bool

if err := provider.SetSPNEGOHeader(&tmpRequest, cannonicalize); err != nil {
if len(responseToken) == 0 && Negotiating == a.state {
a.state = Error
return "", fmt.Errorf("Authentication failed! Unexpected empty token during negotiation!")
}

a.state = Negotiating

token, done, err = a.spnegoProvider.GetToken(url, responseToken)
if err != nil {
a.state = Error
return "", err
}

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

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

// ugly work around the fact that go-spnego only adds an "Authorize" Header and not "Proxy-Authorize"
if a.Mechanism != NoAuth {
authorizeValue = tmpRequest.Header.Get(AuthorizationKey)
a.cycleCount++
if a.cycleCount >= maxCycleCount {
err = fmt.Errorf("Failed to authenticate with %d cycles, stopping now!", maxCycleCount)
}

return authorizeValue, nil
return authorizeValue, err
}

func (a *AuthenticationHandler) IsStopped() bool {
return (a.state == Done || a.state == Error || a.state == Cancel || a.state == Close)
}

func (a *AuthenticationHandler) Reset() {
a.state = Initial
a.cycleCount = 0
a.logger.Println("AuthenticationHandler.Reset()")
}

func (a *AuthenticationHandler) Cancel() {
a.state = Cancel
a.logger.Println("AuthenticationHandler.Cancel()")
}

func (a *AuthenticationHandler) Succesful() {
a.state = Done
a.logger.Println("AuthenticationHandler.Succesful()")
}

func (a *AuthenticationHandler) SetLogger(logger *log.Logger) {
a.logger = logger
a.spnegoProvider.SetLogger(logger)
}

func StringFromAuthenticationMechanism(mechanism AuthenticationMechanism) string {
var result string
switch mechanism {
case NoAuth:
result = "NoAuth"
case Negotiate:
result = "Negotiate"
case Mock:
result = "Mock"
default:
result = "Unknonwn AuthenticationMechanism"
}
return result
return string(mechanism)
}

func AuthenticationMechanismFromString(mechanism string) AuthenticationMechanism {
return AuthenticationMechanism(mechanism)
}
51 changes: 42 additions & 9 deletions cliv2/internal/httpauth/httpauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ func Test_DisableAuthentication(t *testing.T) {
proxyAddr, _ := url.Parse("http://127.0.0.1")
expectedValue := ""

authHandler := httpauth.AuthenticationHandler{
Mechanism: httpauth.NoAuth,
}
authHandler := httpauth.NewHandler(httpauth.NoAuth)

actualValue, err := authHandler.GetAuthorizationValue(proxyAddr)
actualValue, err := authHandler.GetAuthorizationValue(proxyAddr, "")
assert.Nil(t, err)

assert.Equal(t, expectedValue, actualValue)
Expand All @@ -29,13 +27,48 @@ func Test_EnabledAuthentication_Mock(t *testing.T) {
proxyAddr, _ := url.Parse("http://127.0.0.1")
expectedValue := "Mock"

authHandler := httpauth.AuthenticationHandler{
Mechanism: httpauth.Mock,
}
authHandler := httpauth.NewHandler(httpauth.Mock)

actualValue, err := authHandler.GetAuthorizationValue(proxyAddr)
actualValue, err := authHandler.GetAuthorizationValue(proxyAddr, "")
assert.Nil(t, err)

assert.Equal(t, expectedValue, actualValue)
assert.Contains(t, actualValue, expectedValue)

}

func Test_AuthenticationMechanismFromAndToString(t *testing.T) {

testSet := []httpauth.AuthenticationMechanism{
httpauth.Mock,
httpauth.Negotiate,
httpauth.NoAuth,
httpauth.UnknownMechanism,
}

var mechanismConverted httpauth.AuthenticationMechanism
var mechanismString string

for _, mechanism := range testSet {
mechanismString = httpauth.StringFromAuthenticationMechanism(mechanism)
mechanismConverted = httpauth.AuthenticationMechanismFromString(mechanismString)
assert.Equal(t, mechanism, mechanismConverted)
}

}

func Test_LookupSchemeFromAddress(t *testing.T) {
defaultValue := "none"

input := map[string]string{
"snyk.io:443": "https",
"snyk.io:80": "http",
"snyk.io:1080": "socks5",
"snyk.io": defaultValue,
"snyk.io:443:das": defaultValue,
}

for addr, expected := range input {
actual := httpauth.LookupSchemeFromCannonicalAddress(addr, defaultValue)
assert.Equal(t, expected, actual)
}
}
Loading