open-consul/agent/consul/authmethod/kubeauth/k8s_test.go
R.B. Boyer 3ac5a841ec
acl: refactor the authmethod.Validator interface (#7760)
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.
2020-05-01 17:35:28 -05:00

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"