3ac5a841ec
This is a collection of refactors that make upcoming PRs easier to digest. The main change is the introduction of the authmethod.Identity struct. In the one and only current auth method (type=kubernetes) all of the trusted identity attributes are both selectable and projectable, so they were just passed around as a map[string]string. When namespaces were added, this was slightly changed so that the enterprise metadata can also come back from the login operation, so login now returned two fields. Now with some upcoming auth methods it won't be true that all identity attributes will be both selectable and projectable, so rather than update the login function to return 3 pieces of data it seemed worth it to wrap those fields up and give them a proper name.
251 lines
8.1 KiB
Go
251 lines
8.1 KiB
Go
package kubeauth
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/consul/agent/connect"
|
|
"github.com/hashicorp/consul/agent/consul/authmethod"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/go-msgpack/codec"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestStructs_ACLAuthMethod_Kubernetes_MsgpackEncodeDecode(t *testing.T) {
|
|
in := &structs.ACLAuthMethod{
|
|
Name: "k8s",
|
|
Type: "kubernetes",
|
|
Description: "k00b",
|
|
Config: map[string]interface{}{
|
|
"Host": "https://kube.api.internal:8443",
|
|
"CACert": "<my garbage ca cert>",
|
|
"ServiceAccountJWT": "my.fake.jwt",
|
|
},
|
|
RaftIndex: structs.RaftIndex{
|
|
CreateIndex: 5,
|
|
ModifyIndex: 99,
|
|
},
|
|
}
|
|
|
|
expectConfig := &Config{
|
|
Host: "https://kube.api.internal:8443",
|
|
CACert: "<my garbage ca cert>",
|
|
ServiceAccountJWT: "my.fake.jwt",
|
|
}
|
|
|
|
var (
|
|
// This is the common configuration pre-1.7.0
|
|
handle1 = structs.TestingOldPre1dot7MsgpackHandle
|
|
// This is the common configuration post-1.7.0
|
|
handle2 = structs.MsgpackHandle
|
|
)
|
|
|
|
decoderCase := func(t *testing.T, encHandle, decHandle *codec.MsgpackHandle) {
|
|
t.Helper()
|
|
|
|
var buf bytes.Buffer
|
|
enc := codec.NewEncoder(&buf, encHandle)
|
|
require.NoError(t, enc.Encode(in))
|
|
|
|
out := &structs.ACLAuthMethod{}
|
|
dec := codec.NewDecoder(&buf, decHandle)
|
|
require.NoError(t, dec.Decode(out))
|
|
|
|
var config Config
|
|
require.NoError(t, authmethod.ParseConfig(in.Config, &config))
|
|
|
|
out.Config = in.Config // no longer care about how this field decoded
|
|
require.Equal(t, in, out)
|
|
require.Equal(t, expectConfig, &config)
|
|
// TODO: verify json?
|
|
}
|
|
|
|
t.Run("old encoder and old decoder", func(t *testing.T) {
|
|
decoderCase(t, handle1, handle1)
|
|
})
|
|
t.Run("old encoder and new decoder", func(t *testing.T) {
|
|
decoderCase(t, handle1, handle2)
|
|
})
|
|
t.Run("new encoder and old decoder", func(t *testing.T) {
|
|
decoderCase(t, handle2, handle1)
|
|
})
|
|
t.Run("new encoder and new decoder", func(t *testing.T) {
|
|
decoderCase(t, handle2, handle2)
|
|
})
|
|
}
|
|
|
|
func TestNewIdentity(t *testing.T) {
|
|
testSrv := StartTestAPIServer(t)
|
|
defer testSrv.Stop()
|
|
|
|
method := &structs.ACLAuthMethod{
|
|
Name: "test-k8s",
|
|
Description: "k8s test",
|
|
Type: "kubernetes",
|
|
Config: map[string]interface{}{
|
|
"Host": testSrv.Addr(),
|
|
"CACert": testSrv.CACert(),
|
|
"ServiceAccountJWT": goodJWT_A,
|
|
},
|
|
}
|
|
validator, err := NewValidator(method)
|
|
require.NoError(t, err)
|
|
|
|
id := validator.NewIdentity()
|
|
authmethod.RequireIdentityMatch(t, id, map[string]string{
|
|
"serviceaccount.namespace": "",
|
|
"serviceaccount.name": "",
|
|
"serviceaccount.uid": "",
|
|
},
|
|
`serviceaccount.namespace == ""`,
|
|
`serviceaccount.name == ""`,
|
|
`serviceaccount.uid == ""`,
|
|
)
|
|
}
|
|
|
|
func TestValidateLogin(t *testing.T) {
|
|
testSrv := StartTestAPIServer(t)
|
|
defer testSrv.Stop()
|
|
|
|
testSrv.AuthorizeJWT(goodJWT_A)
|
|
testSrv.SetAllowedServiceAccount(
|
|
"default",
|
|
"demo",
|
|
"76091af4-4b56-11e9-ac4b-708b11801cbe",
|
|
"",
|
|
goodJWT_B,
|
|
)
|
|
|
|
method := &structs.ACLAuthMethod{
|
|
Name: "test-k8s",
|
|
Description: "k8s test",
|
|
Type: "kubernetes",
|
|
Config: map[string]interface{}{
|
|
"Host": testSrv.Addr(),
|
|
"CACert": testSrv.CACert(),
|
|
"ServiceAccountJWT": goodJWT_A,
|
|
},
|
|
}
|
|
validator, err := NewValidator(method)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("invalid bearer token", func(t *testing.T) {
|
|
_, err := validator.ValidateLogin(context.Background(), "invalid")
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("valid bearer token", func(t *testing.T) {
|
|
id, err := validator.ValidateLogin(context.Background(), goodJWT_B)
|
|
require.NoError(t, err)
|
|
|
|
authmethod.RequireIdentityMatch(t, id, map[string]string{
|
|
"serviceaccount.namespace": "default",
|
|
"serviceaccount.name": "demo",
|
|
"serviceaccount.uid": "76091af4-4b56-11e9-ac4b-708b11801cbe",
|
|
},
|
|
`serviceaccount.namespace == default`,
|
|
`serviceaccount.name == "demo"`,
|
|
`serviceaccount.uid == "76091af4-4b56-11e9-ac4b-708b11801cbe"`,
|
|
)
|
|
})
|
|
|
|
// annotate the account
|
|
testSrv.SetAllowedServiceAccount(
|
|
"default",
|
|
"demo",
|
|
"76091af4-4b56-11e9-ac4b-708b11801cbe",
|
|
"alternate-name",
|
|
goodJWT_B,
|
|
)
|
|
|
|
t.Run("valid bearer token with annotation", func(t *testing.T) {
|
|
id, err := validator.ValidateLogin(context.Background(), goodJWT_B)
|
|
require.NoError(t, err)
|
|
|
|
authmethod.RequireIdentityMatch(t, id, map[string]string{
|
|
"serviceaccount.namespace": "default",
|
|
"serviceaccount.name": "alternate-name",
|
|
"serviceaccount.uid": "76091af4-4b56-11e9-ac4b-708b11801cbe",
|
|
},
|
|
`serviceaccount.namespace == default`,
|
|
`serviceaccount.name == "alternate-name"`,
|
|
`serviceaccount.uid == "76091af4-4b56-11e9-ac4b-708b11801cbe"`,
|
|
)
|
|
})
|
|
}
|
|
|
|
func TestNewValidator(t *testing.T) {
|
|
ca := connect.TestCA(t, nil)
|
|
|
|
type AM = *structs.ACLAuthMethod
|
|
|
|
makeAuthMethod := func(f func(method AM)) *structs.ACLAuthMethod {
|
|
method := &structs.ACLAuthMethod{
|
|
Name: "test-k8s",
|
|
Description: "k8s test",
|
|
Type: "kubernetes",
|
|
Config: map[string]interface{}{
|
|
"Host": "https://abc:8443",
|
|
"CACert": ca.RootCert,
|
|
"ServiceAccountJWT": goodJWT_A,
|
|
},
|
|
}
|
|
if f != nil {
|
|
f(method)
|
|
}
|
|
return method
|
|
}
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
method *structs.ACLAuthMethod
|
|
ok bool
|
|
}{
|
|
// bad
|
|
{"wrong type", makeAuthMethod(func(method AM) {
|
|
method.Type = "invalid"
|
|
}), false},
|
|
{"extra config", makeAuthMethod(func(method AM) {
|
|
method.Config["extra"] = "config"
|
|
}), false},
|
|
{"wrong type of config", makeAuthMethod(func(method AM) {
|
|
method.Config["Host"] = []int{12345}
|
|
}), false},
|
|
{"missing host", makeAuthMethod(func(method AM) {
|
|
delete(method.Config, "Host")
|
|
}), false},
|
|
{"missing ca cert", makeAuthMethod(func(method AM) {
|
|
delete(method.Config, "CACert")
|
|
}), false},
|
|
{"invalid ca cert", makeAuthMethod(func(method AM) {
|
|
method.Config["CACert"] = "invalid"
|
|
}), false},
|
|
{"invalid jwt", makeAuthMethod(func(method AM) {
|
|
method.Config["ServiceAccountJWT"] = "invalid"
|
|
}), false},
|
|
{"garbage host", makeAuthMethod(func(method AM) {
|
|
method.Config["Host"] = "://:12345"
|
|
}), false},
|
|
// good
|
|
{"normal", makeAuthMethod(nil), true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
v, err := NewValidator(test.method)
|
|
if test.ok {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, v)
|
|
} else {
|
|
require.NotNil(t, err)
|
|
require.Nil(t, v)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// 'default/admin'
|
|
const goodJWT_A = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImFkbWluLXRva2VuLXFsejQyIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNzM4YmMyNTEtNjUzMi0xMWU5LWI2N2YtNDhlNmM4YjhlY2I1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6YWRtaW4ifQ.ixMlnWrAG7NVuTTKu8cdcYfM7gweS3jlKaEsIBNGOVEjPE7rtXtgMkAwjQTdYR08_0QBjkgzy5fQC5ZNyglSwONJ-bPaXGvhoH1cTnRi1dz9H_63CfqOCvQP1sbdkMeRxNTGVAyWZT76rXoCUIfHP4LY2I8aab0KN9FTIcgZRF0XPTtT70UwGIrSmRpxW38zjiy2ymWL01cc5VWGhJqVysmWmYk3wNp0h5N57H_MOrz4apQR4pKaamzskzjLxO55gpbmZFC76qWuUdexAR7DT2fpbHLOw90atN_NlLMY-VrXyW3-Ei5EhYaVreMB9PSpKwkrA4jULITohV-sxpa1LA"
|
|
|
|
// 'default/demo'
|
|
const goodJWT_B = "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlbW8tdG9rZW4ta21iOW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVtbyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6Ijc2MDkxYWY0LTRiNTYtMTFlOS1hYzRiLTcwOGIxMTgwMWNiZSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlbW8ifQ.ZiAHjijBAOsKdum0Aix6lgtkLkGo9_Tu87dWQ5Zfwnn3r2FejEWDAnftTft1MqqnMzivZ9Wyyki5ZjQRmTAtnMPJuHC-iivqY4Wh4S6QWCJ1SivBv5tMZR79t5t8mE7R1-OHwst46spru1pps9wt9jsA04d3LpV0eeKYgdPTVaQKklxTm397kIMUugA6yINIBQ3Rh8eQqBgNwEmL4iqyYubzHLVkGkoP9MJikFI05vfRiHtYr-piXz6JFDzXMQj9rW6xtMmrBSn79ChbyvC5nz-Nj2rJPnHsb_0rDUbmXY5PpnMhBpdSH-CbZ4j8jsiib6DtaGJhVZeEQ1GjsFAZwQ"
|