Skip to content

Commit

Permalink
feat: Implemented Basic Auth scheme (argoproj#2093)
Browse files Browse the repository at this point in the history
  • Loading branch information
sarabala1979 committed Jan 30, 2020
1 parent 7611b9f commit 7a19f85
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 20 deletions.
12 changes: 6 additions & 6 deletions cmd/argo/commands/client/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,21 @@ func GetClientConn() *grpc.ClientConn {
}

func GetContext() context.Context {
token := GetBearerToken()
if len(token) == 0 {
authString := GetAuthString()
if authString == "" {
return context.Background()
}
return metadata.NewOutgoingContext(context.Background(), metadata.Pairs("authorization", "Bearer "+token))
return metadata.NewOutgoingContext(context.Background(), metadata.Pairs("authorization", authString))
}

func GetBearerToken() string {
func GetAuthString() string {
restConfig, err := Config.ClientConfig()
if err != nil {
log.Fatal(err)
}
token, err := kubeconfig.GetBearerToken(restConfig)
authString, err := kubeconfig.GetAuthString(restConfig)
if err != nil {
log.Fatal(err)
}
return token
return authString
}
2 changes: 1 addition & 1 deletion cmd/argo/commands/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func NewTokenCommand() *cobra.Command {
cmd.HelpFunc()(cmd, args)
os.Exit(1)
}
fmt.Print(client.GetBearerToken())
fmt.Print(client.GetAuthString())
},
}
}
14 changes: 7 additions & 7 deletions server/auth/gatekeeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package auth
import (
"context"
"net/http"
"strings"

grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
Expand Down Expand Up @@ -96,10 +95,10 @@ func (s Gatekeeper) useClientAuth(token string) bool {
return false
}

func getToken(md metadata.MD) string {
func getAuthHeader(md metadata.MD) string {
// looks for the HTTP header `Authorization: Bearer ...`
for _, t := range md.Get("authorization") {
return strings.TrimPrefix(t, "Bearer ")
return t
}
// check the HTTP cookie
for _, t := range md.Get("grpcgateway-cookie") {
Expand All @@ -108,7 +107,7 @@ func getToken(md metadata.MD) string {
request := http.Request{Header: header}
token, err := request.Cookie("authorization")
if err == nil {
return strings.TrimPrefix(token.Value, "Bearer ")
return token.Value
}
}
return ""
Expand All @@ -127,13 +126,14 @@ func (s Gatekeeper) getClients(ctx context.Context) (versioned.Interface, kubern
return nil, nil, status.Error(codes.Unauthenticated, "unable to get metadata from incoming context")
}

token := getToken(md)
authString := getAuthHeader(md)

if !s.useClientAuth(token) {
if !s.useClientAuth(authString) {
return s.wfClient, s.kubeClient, nil
}

restConfig, err := kubeconfig.GetRestConfig(token)
restConfig, err := kubeconfig.GetRestConfig(authString)

if err != nil {
return nil, nil, status.Errorf(codes.Unauthenticated, "failed to create REST config: %v", err)
}
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/argo_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (s *ArgoServerSuite) TestCookieAuth() {
defer func() { s.bearerToken = token }()
s.bearerToken = ""
s.e(s.T()).GET("/api/v1/workflows/argo").
WithHeader("Cookie", "authorization="+token).
WithHeader("Cookie", "authorization=Bearer "+token).
Expect().
Status(200)
}
Expand Down Expand Up @@ -677,7 +677,7 @@ func (s *ArgoServerSuite) TestArtifactServer() {
defer func() { s.bearerToken = token }()
s.bearerToken = ""
s.e(t).GET("/artifacts-by-uid/{uid}/basic/main-logs", uid).
WithHeader("Cookie", "authorization="+token).
WithHeader("Cookie", "authorization=Bearer "+token).
Expect().
Status(200)
})
Expand Down
13 changes: 10 additions & 3 deletions test/e2e/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,16 @@ func (s *CLISuite) TestCompletion() {
func (s *CLISuite) TestToken() {
s.Given().RunCli([]string{"token"}, func(t *testing.T, output string, err error) {
assert.NoError(t, err)
token, err := s.GetServiceAccountToken()
assert.NoError(t, err)
assert.Equal(t, token, output)
var authString, token string
token = s.GetBasicAuthToken()
if token == "" {
token, err = s.GetServiceAccountToken()
assert.NoError(t, err)
authString = "Bearer " + token
} else {
authString = "Basic " + token
}
assert.Equal(t, authString, output)
})
}

Expand Down
9 changes: 9 additions & 0 deletions test/e2e/fixtures/e2e_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fixtures

import (
"bufio"
"encoding/base64"
"fmt"
"strings"
"testing"
Expand Down Expand Up @@ -140,6 +141,14 @@ func (s *E2ESuite) BeforeTest(_, _ string) {
s.Persistence.DeleteEverything()
}

func (s *E2ESuite) GetBasicAuthToken() string {
if s.RestConfig.Username == "" {
return ""
}
auth := s.RestConfig.Username + ":" + s.RestConfig.Password
return base64.StdEncoding.EncodeToString([]byte(auth))
}

func (s *E2ESuite) GetServiceAccountToken() (string, error) {
// create the clientset
clientset, err := kubernetes.NewForConfig(s.RestConfig)
Expand Down
90 changes: 89 additions & 1 deletion util/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kubeconfig

import (
"encoding/base64"
"net/http"
"os"
"strings"
Expand All @@ -13,16 +14,60 @@ import (
"k8s.io/client-go/transport"
)

const (
BasicAuthScheme = "Basic"
BearerAuthScheme = "Bearer"
)

// get the default one from the filesystem
func DefaultRestConfig() (*restclient.Config, error) {
rules := clientcmd.NewDefaultClientConfigLoadingRules()
config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{})
return config.ClientConfig()
}

// convert a bearer token into a REST config
func IsBasicAuthScheme(token string) bool {
return strings.HasPrefix(token, BasicAuthScheme)
}

func IsBearerAuthScheme(token string) bool {
return strings.HasPrefix(token, BearerAuthScheme)
}

func GetRestConfig(token string) (*restclient.Config, error) {

if IsBasicAuthScheme(token) {
token = strings.TrimSpace(strings.TrimPrefix(token, BasicAuthScheme))
username, password, ok := decodeBasicAuthToken(token)
if !ok {
return nil, errors.New("Error parsing Basic Authentication")
}
return GetBasicRestConfig(username, password)
}
if IsBearerAuthScheme(token) {
token = strings.TrimSpace(strings.TrimPrefix(token, BearerAuthScheme))
return GetBearerRestConfig(token)
}
return nil, errors.New("Unsupported authentication scheme")
}

// convert a basic token (username, password) into a REST config
func GetBasicRestConfig(username, password string) (*restclient.Config, error) {

restConfig, err := DefaultRestConfig()
if err != nil {
return nil, err
}
restConfig.BearerToken = ""
restConfig.BearerTokenFile = ""
restConfig.Username = username
restConfig.Password = password
return restConfig, nil
}

// convert a bearer token into a REST config
func GetBearerRestConfig(token string) (*restclient.Config, error) {

restConfig, err := DefaultRestConfig()
if err != nil {
return nil, err
Expand All @@ -37,6 +82,27 @@ func GetRestConfig(token string) (*restclient.Config, error) {
return restConfig, nil
}

//Return the AuthString include Auth type(Basic or Bearer)
func GetAuthString(in *restclient.Config) (string, error) {
//Checking Basic Auth
if in.Username != "" {
token, err := GetBasicAuthToken(in)
return BasicAuthScheme + " " + token, err
}

token, err := GetBearerToken(in)
return BearerAuthScheme + " " + token, err
}

func GetBasicAuthToken(in *restclient.Config) (string, error) {

if in == nil {
return "", errors.Errorf("RestClient can't be nil")
}

return encodeBasicAuthToken(in.Username, in.Password), nil
}

// convert the REST config into a bearer token
func GetBearerToken(in *restclient.Config) (string, error) {

Expand All @@ -48,6 +114,9 @@ func GetBearerToken(in *restclient.Config) (string, error) {
return token, nil
}

if in == nil {
return "", errors.Errorf("RestClient can't be nil")
}
if in.ExecProvider != nil {
tc, err := in.TransportConfig()
if err != nil {
Expand Down Expand Up @@ -107,3 +176,22 @@ func GetBearerToken(in *restclient.Config) (string, error) {
func getEnvToken() string {
return os.Getenv("ARGO_TOKEN")
}

func encodeBasicAuthToken(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}

func decodeBasicAuthToken(auth string) (username, password string, ok bool) {

c, err := base64.StdEncoding.DecodeString(auth)
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
84 changes: 84 additions & 0 deletions util/kubeconfig/kubeconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package kubeconfig

import (
"io/ioutil"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

const bearerToken = "bearertoken"

const config = `
apiVersion: v1
clusters:
- cluster:
server: https://localhost:6443
name: k3s-default
contexts:
- context:
cluster: k3s-default
namespace: argo
user: k3s-default
name: k3s-default
current-context: k3s-default
kind: Config
preferences: {}
users:
- name: k3s-default
user:
password: admin
username: admin
`

func Test_BasicAuthString(t *testing.T) {

t.Run("Basic Auth", func(t *testing.T) {

restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(config))
assert.NoError(t, err)
authString, err := GetAuthString(restConfig)
assert.NoError(t, err)
assert.True(t, IsBasicAuthScheme(authString))
token := strings.TrimSpace(strings.TrimPrefix(authString, BasicAuthScheme))
uname, pwd, ok := decodeBasicAuthToken(token)
if assert.True(t, ok) {
assert.Equal(t, "admin", uname)
assert.Equal(t, "admin", pwd)
}
file, err := ioutil.TempFile("", "config.yaml")
assert.NoError(t, err)
_, err = file.WriteString(config)
assert.NoError(t, err)
err = file.Close()
assert.NoError(t, err)
os.Setenv("KUBECONFIG", file.Name())
config, err := GetRestConfig(authString)
if assert.NoError(t, err) {
assert.Equal(t, "admin", config.Username)
assert.Equal(t, "admin", config.Password)
}

})
}

func Test_BearerAuthString(t *testing.T) {

restConfig := rest.Config{}

t.Run("Bearer Auth", func(t *testing.T) {
os.Setenv("ARGO_TOKEN", bearerToken)
authString, err := GetAuthString(&restConfig)
assert.NoError(t, err)
assert.True(t, IsBearerAuthScheme(authString))
config, err := GetRestConfig(authString)
assert.NoError(t, err)
if assert.NoError(t, err) {
assert.Equal(t, bearerToken, config.BearerToken)
}
})
}

0 comments on commit 7a19f85

Please sign in to comment.