From c8d2277bb64e040103516d80122a90ab4f94b3bd Mon Sep 17 00:00:00 2001 From: Jakub Dyszkiewicz Date: Fri, 16 Oct 2020 10:07:17 +0200 Subject: [PATCH] feat(kuma-cp) dataplane token bound to the DP type (#1069) Signed-off-by: Jakub Dyszkiewicz --- .../cmd/completion/testdata/bash.golden | 3 + .../cmd/completion/testdata/zsh.golden | 1 + .../cmd/generate/generate_dataplane_token.go | 7 +- .../generate/generate_dataplane_token_test.go | 54 +++-- app/kumactl/pkg/tokens/client.go | 5 +- app/kumactl/pkg/tokens/client_test.go | 6 +- pkg/sds/auth/common/common_suite_test.go | 13 - pkg/sds/auth/common/identity.go | 25 -- pkg/sds/auth/common/identity_test.go | 105 -------- pkg/sds/auth/credential.go | 27 --- pkg/sds/auth/interfaces.go | 18 -- pkg/sds/auth/k8s/authenticator.go | 75 ------ pkg/sds/auth/universal/auth_test.go | 226 ------------------ pkg/sds/auth/universal/authenticator.go | 85 ------- pkg/sds/auth/universal/noop_authenticator.go | 29 --- .../auth/universal/noop_authenticator_test.go | 79 ------ .../auth/universal/universal_suite_test.go | 13 - pkg/sds/provider/ca/provider.go | 3 +- pkg/sds/provider/identity/provider.go | 3 +- pkg/sds/provider/interfaces.go | 10 +- pkg/sds/server/auth_callbacks.go | 92 ------- pkg/sds/server/components.go | 64 ----- pkg/sds/server/reconciler.go | 16 +- pkg/sds/server/server.go | 6 +- pkg/sds/server/server_test.go | 13 +- pkg/tokens/builtin/issuer/issuer.go | 11 + .../server/types/dataplane_token_request.go | 1 + pkg/tokens/builtin/server/webservice.go | 1 + pkg/xds/auth/universal/auth_test.go | 116 ++++++--- pkg/xds/auth/universal/authenticator.go | 16 ++ pkg/xds/envoy/tls.go | 10 +- test/framework/universal_controlplane.go | 6 +- 32 files changed, 201 insertions(+), 938 deletions(-) delete mode 100644 pkg/sds/auth/common/common_suite_test.go delete mode 100644 pkg/sds/auth/common/identity.go delete mode 100644 pkg/sds/auth/common/identity_test.go delete mode 100644 pkg/sds/auth/credential.go delete mode 100644 pkg/sds/auth/interfaces.go delete mode 100644 pkg/sds/auth/k8s/authenticator.go delete mode 100644 pkg/sds/auth/universal/auth_test.go delete mode 100644 pkg/sds/auth/universal/authenticator.go delete mode 100644 pkg/sds/auth/universal/noop_authenticator.go delete mode 100644 pkg/sds/auth/universal/noop_authenticator_test.go delete mode 100644 pkg/sds/auth/universal/universal_suite_test.go delete mode 100644 pkg/sds/server/auth_callbacks.go diff --git a/app/kumactl/cmd/completion/testdata/bash.golden b/app/kumactl/cmd/completion/testdata/bash.golden index cbad5693a6ff..3dbd28c98dac 100644 --- a/app/kumactl/cmd/completion/testdata/bash.golden +++ b/app/kumactl/cmd/completion/testdata/bash.golden @@ -736,6 +736,9 @@ _kumactl_generate_dataplane-token() flags+=("--tag=") two_word_flags+=("--tag") local_nonpersistent_flags+=("--tag=") + flags+=("--type=") + two_word_flags+=("--type") + local_nonpersistent_flags+=("--type=") flags+=("--config-file=") two_word_flags+=("--config-file") flags+=("--log-level=") diff --git a/app/kumactl/cmd/completion/testdata/zsh.golden b/app/kumactl/cmd/completion/testdata/zsh.golden index 67fd7b770fd6..313e42bcf480 100644 --- a/app/kumactl/cmd/completion/testdata/zsh.golden +++ b/app/kumactl/cmd/completion/testdata/zsh.golden @@ -284,6 +284,7 @@ function _kumactl_generate_dataplane-token { _arguments \ '--dataplane[name of the Dataplane]:' \ '--tag[required tag values for dataplane (split values by comma to provide multiple values)]:' \ + '--type[type of the Dataplane ("dataplane", "ingress")]:' \ '--config-file[path to the configuration file to use]:' \ '--log-level[log level: one of off|info|debug]:' \ '(-m --mesh)'{-m,--mesh}'[mesh to use]:' diff --git a/app/kumactl/cmd/generate/generate_dataplane_token.go b/app/kumactl/cmd/generate/generate_dataplane_token.go index 7a4ee8579400..b076371736f3 100644 --- a/app/kumactl/cmd/generate/generate_dataplane_token.go +++ b/app/kumactl/cmd/generate/generate_dataplane_token.go @@ -14,6 +14,7 @@ type generateDataplaneTokenContext struct { args struct { dataplane string + dpType string tags map[string]string } } @@ -31,6 +32,9 @@ $ kumactl generate dataplane-token --mesh demo --dataplane demo-01 Generate token bound by mesh $ kumactl generate dataplane-token --mesh demo +Generate Ingress token +$ kumactl generate dataplane-token --type ingress + Generate token bound by tag $ kumactl generate dataplane-token --mesh demo --tag kuma.io/service=web,web-api `, @@ -44,7 +48,7 @@ $ kumactl generate dataplane-token --mesh demo --tag kuma.io/service=web,web-api for k, v := range ctx.args.tags { tags[k] = strings.Split(v, ",") } - token, err := client.Generate(ctx.args.dataplane, pctx.Args.Mesh, tags) + token, err := client.Generate(ctx.args.dataplane, pctx.Args.Mesh, tags, ctx.args.dpType) if err != nil { return errors.Wrap(err, "failed to generate a dataplane token") } @@ -53,6 +57,7 @@ $ kumactl generate dataplane-token --mesh demo --tag kuma.io/service=web,web-api }, } cmd.Flags().StringVar(&ctx.args.dataplane, "dataplane", "", "name of the Dataplane") + cmd.Flags().StringVar(&ctx.args.dpType, "type", "", `type of the Dataplane ("dataplane", "ingress")`) cmd.Flags().StringToStringVar(&ctx.args.tags, "tag", nil, "required tag values for dataplane (split values by comma to provide multiple values)") return cmd } diff --git a/app/kumactl/cmd/generate/generate_dataplane_token_test.go b/app/kumactl/cmd/generate/generate_dataplane_token_test.go index 62c0fa85d1e3..f790634a0804 100644 --- a/app/kumactl/cmd/generate/generate_dataplane_token_test.go +++ b/app/kumactl/cmd/generate/generate_dataplane_token_test.go @@ -6,9 +6,11 @@ import ( "fmt" . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" "github.com/spf13/cobra" + mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" "github.com/kumahq/kuma/app/kumactl/cmd" kumactl_cmd "github.com/kumahq/kuma/app/kumactl/pkg/cmd" "github.com/kumahq/kuma/app/kumactl/pkg/tokens" @@ -24,11 +26,11 @@ type staticDataplaneTokenGenerator struct { var _ tokens.DataplaneTokenClient = &staticDataplaneTokenGenerator{} -func (s *staticDataplaneTokenGenerator) Generate(name string, mesh string, tags map[string][]string) (string, error) { +func (s *staticDataplaneTokenGenerator) Generate(name string, mesh string, tags map[string][]string, dpType string) (string, error) { if s.err != nil { return "", s.err } - return fmt.Sprintf("token-for-%s-%s", name, mesh), nil + return fmt.Sprintf("token-for-%s-%s-%s-%s", name, mesh, mesh_proto.MultiValueTagSetFrom(tags).String(), dpType), nil } var _ = Describe("kumactl generate dataplane-token", func() { @@ -64,29 +66,31 @@ var _ = Describe("kumactl generate dataplane-token", func() { rootCmd.SetOut(buf) }) - It("should generate a token", func() { - // when - rootCmd.SetArgs([]string{"generate", "dataplane-token", "--dataplane=example", "--mesh=demo"}) - err := rootCmd.Execute() - - // then - Expect(err).ToNot(HaveOccurred()) - - // and - Expect(buf.String()).To(Equal("token-for-example-demo")) - }) - - It("should generate a token for default mesh when it is not specified", func() { - // when - rootCmd.SetArgs([]string{"generate", "dataplane-token", "--dataplane=example"}) - err := rootCmd.Execute() - - // then - Expect(err).ToNot(HaveOccurred()) - - // and - Expect(buf.String()).To(Equal("token-for-example-default")) - }) + type testCase struct { + args []string + result string + } + DescribeTable("should generate token", + func(given testCase) { + // when + rootCmd.SetArgs(given.args) + err := rootCmd.Execute() + + // then + Expect(err).ToNot(HaveOccurred()) + + // and + Expect(buf.String()).To(Equal(given.result)) + }, + Entry("for default mesh when it is not specified", testCase{ + args: []string{"generate", "dataplane-token", "--dataplane=example"}, + result: "token-for-example-default--", + }), + Entry("for all arguments", testCase{ + args: []string{"generate", "dataplane-token", "--mesh=demo", "--dataplane=example", "--type=dataplane", "--tag", "kuma.io/service=web"}, + result: "token-for-example-demo-kuma.io/service=web-dataplane", + }), + ) It("should write error when generating token fails", func() { // setup diff --git a/app/kumactl/pkg/tokens/client.go b/app/kumactl/pkg/tokens/client.go index 80ba2e08aea2..94ec09a4d8b1 100644 --- a/app/kumactl/pkg/tokens/client.go +++ b/app/kumactl/pkg/tokens/client.go @@ -43,7 +43,7 @@ func NewDataplaneTokenClient(address string, config *kumactl_config.Context_Admi } type DataplaneTokenClient interface { - Generate(name string, mesh string, tags map[string][]string) (string, error) + Generate(name string, mesh string, tags map[string][]string, dpType string) (string, error) } type httpDataplaneTokenClient struct { @@ -52,11 +52,12 @@ type httpDataplaneTokenClient struct { var _ DataplaneTokenClient = &httpDataplaneTokenClient{} -func (h *httpDataplaneTokenClient) Generate(name string, mesh string, tags map[string][]string) (string, error) { +func (h *httpDataplaneTokenClient) Generate(name string, mesh string, tags map[string][]string, dpType string) (string, error) { tokenReq := &types.DataplaneTokenRequest{ Name: name, Mesh: mesh, Tags: tags, + Type: dpType, } reqBytes, err := json.Marshal(tokenReq) if err != nil { diff --git a/app/kumactl/pkg/tokens/client_test.go b/app/kumactl/pkg/tokens/client_test.go index 89969e38952e..411d969dddcc 100644 --- a/app/kumactl/pkg/tokens/client_test.go +++ b/app/kumactl/pkg/tokens/client_test.go @@ -89,12 +89,12 @@ var _ = Describe("Tokens Client", func() { // wait for server Eventually(func() error { - _, err := client.Generate("example", "default", nil) + _, err := client.Generate("example", "default", nil, "dataplane") return err }, "5s", "100ms").ShouldNot(HaveOccurred()) // when - token, err := client.Generate("example", "default", nil) + token, err := client.Generate("example", "default", nil, "dataplane") // then Expect(err).ToNot(HaveOccurred()) @@ -129,7 +129,7 @@ var _ = Describe("Tokens Client", func() { Expect(err).ToNot(HaveOccurred()) // when - _, err = client.Generate("example", "default", nil) + _, err = client.Generate("example", "default", nil, "dataplane") // then Expect(err).To(MatchError("unexpected status code 500. Expected 200")) diff --git a/pkg/sds/auth/common/common_suite_test.go b/pkg/sds/auth/common/common_suite_test.go deleted file mode 100644 index 76433429a925..000000000000 --- a/pkg/sds/auth/common/common_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package common_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestCommon(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Sds Auth Common Suite") -} diff --git a/pkg/sds/auth/common/identity.go b/pkg/sds/auth/common/identity.go deleted file mode 100644 index 3b050ab17591..000000000000 --- a/pkg/sds/auth/common/identity.go +++ /dev/null @@ -1,25 +0,0 @@ -package common - -import ( - "context" - - "github.com/pkg/errors" - - mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" - core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" - core_xds "github.com/kumahq/kuma/pkg/core/xds" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" -) - -type DataplaneResolver func(context.Context, core_xds.ProxyId) (*core_mesh.DataplaneResource, error) - -func GetDataplaneIdentity(dataplane *core_mesh.DataplaneResource) (sds_auth.Identity, error) { - services := dataplane.Spec.TagSet().Values(mesh_proto.ServiceTag) - if len(services) == 0 { - return sds_auth.Identity{}, errors.Errorf("Dataplane has no services associated with it") - } - return sds_auth.Identity{ - Mesh: dataplane.Meta.GetMesh(), - Services: services, - }, nil -} diff --git a/pkg/sds/auth/common/identity_test.go b/pkg/sds/auth/common/identity_test.go deleted file mode 100644 index e9e404541cc3..000000000000 --- a/pkg/sds/auth/common/identity_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package common_test - -import ( - mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" - core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" - auth_common "github.com/kumahq/kuma/pkg/sds/auth/common" - "github.com/kumahq/kuma/pkg/test/resources/model" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("GetDataplaneIdentity()", func() { - It("should get identity from Dataplane with inbounds", func() { - // given - dpRes := core_mesh.DataplaneResource{ - Meta: &model.ResourceMeta{ - Mesh: "demo", - Name: "dp-1", - }, - Spec: mesh_proto.Dataplane{ - Networking: &mesh_proto.Dataplane_Networking{ - Address: "127.0.0.1", - Inbound: []*mesh_proto.Dataplane_Networking_Inbound{ - { - Port: 8080, - ServicePort: 80, - Tags: map[string]string{ - "kuma.io/service": "backend", - }, - }, - { - Port: 9080, - ServicePort: 90, - Tags: map[string]string{ - "kuma.io/service": "backend-api", - }, - }, - }, - }, - }, - } - - // when - id, err := auth_common.GetDataplaneIdentity(&dpRes) - - // then - Expect(err).ToNot(HaveOccurred()) - - // and - Expect(id).To(Equal(sds_auth.Identity{ - Mesh: "demo", - Services: []string{"backend", "backend-api"}, - })) - }) - - It("should get identity from Dataplane with gateway", func() { - // given - dpRes := core_mesh.DataplaneResource{ - Meta: &model.ResourceMeta{ - Mesh: "demo", - Name: "dp-1", - }, - Spec: mesh_proto.Dataplane{ - Networking: &mesh_proto.Dataplane_Networking{ - Gateway: &mesh_proto.Dataplane_Networking_Gateway{ - Tags: map[string]string{ - "kuma.io/service": "edge", - }, - }, - }, - }, - } - - // when - id, err := auth_common.GetDataplaneIdentity(&dpRes) - - // then - Expect(err).ToNot(HaveOccurred()) - - // and - Expect(id).To(Equal(sds_auth.Identity{ - Mesh: "demo", - Services: []string{"edge"}, - })) - }) - - It("should throw an error on dataplane without services", func() { - // given - dpRes := core_mesh.DataplaneResource{ - Meta: &model.ResourceMeta{ - Mesh: "demo", - Name: "dp-1", - }, - Spec: mesh_proto.Dataplane{}, - } - - // when - _, err := auth_common.GetDataplaneIdentity(&dpRes) - - // then - Expect(err).To(MatchError("Dataplane has no services associated with it")) - }) -}) diff --git a/pkg/sds/auth/credential.go b/pkg/sds/auth/credential.go deleted file mode 100644 index 57f23c197638..000000000000 --- a/pkg/sds/auth/credential.go +++ /dev/null @@ -1,27 +0,0 @@ -package auth - -import ( - "context" - - "github.com/pkg/errors" - - "google.golang.org/grpc/metadata" -) - -const ( - authorization = "authorization" -) - -func ExtractCredential(ctx context.Context) (Credential, error) { - metadata, ok := metadata.FromIncomingContext(ctx) - if !ok { - return "", errors.Errorf("SDS request has no metadata") - } - if values, ok := metadata[authorization]; ok { - if len(values) != 1 { - return "", errors.Errorf("SDS request must have exactly 1 %q header, got %d", authorization, len(values)) - } - return Credential(values[0]), nil - } - return "", nil -} diff --git a/pkg/sds/auth/interfaces.go b/pkg/sds/auth/interfaces.go deleted file mode 100644 index 5c2098812eaa..000000000000 --- a/pkg/sds/auth/interfaces.go +++ /dev/null @@ -1,18 +0,0 @@ -package auth - -import ( - "context" - - core_xds "github.com/kumahq/kuma/pkg/core/xds" -) - -type Credential string - -type Identity struct { - Mesh string - Services []string -} - -type Authenticator interface { - Authenticate(ctx context.Context, proxyId core_xds.ProxyId, credential Credential) (Identity, error) -} diff --git a/pkg/sds/auth/k8s/authenticator.go b/pkg/sds/auth/k8s/authenticator.go deleted file mode 100644 index 2fd085d4c789..000000000000 --- a/pkg/sds/auth/k8s/authenticator.go +++ /dev/null @@ -1,75 +0,0 @@ -package stub - -import ( - "context" - "strings" - - "github.com/pkg/errors" - - core_xds "github.com/kumahq/kuma/pkg/core/xds" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" - common_auth "github.com/kumahq/kuma/pkg/sds/auth/common" - util_k8s "github.com/kumahq/kuma/pkg/util/k8s" - - kube_auth "k8s.io/api/authentication/v1" - kube_client "sigs.k8s.io/controller-runtime/pkg/client" -) - -func New(client kube_client.Client, dataplaneResolver common_auth.DataplaneResolver) sds_auth.Authenticator { - return &kubeAuthenticator{ - client: client, - dataplaneResolver: dataplaneResolver, - } -} - -type kubeAuthenticator struct { - client kube_client.Client - dataplaneResolver common_auth.DataplaneResolver -} - -func (k *kubeAuthenticator) Authenticate(ctx context.Context, proxyId core_xds.ProxyId, credential sds_auth.Credential) (sds_auth.Identity, error) { - if err := k.reviewToken(ctx, proxyId, credential); err != nil { - return sds_auth.Identity{}, err - } - // at this point we know that proxyId belongs to the same namespace as token. - // since legacy k8s tokens are not bound to a specific Pod, - // we have to rely on information included into proxyId. - dataplane, err := k.dataplaneResolver(ctx, proxyId) - if err != nil { - return sds_auth.Identity{}, errors.Wrapf(err, "unable to find Dataplane for proxy %q", proxyId) - } - return common_auth.GetDataplaneIdentity(dataplane) -} - -func (k *kubeAuthenticator) reviewToken(ctx context.Context, proxyId core_xds.ProxyId, credential sds_auth.Credential) error { - if credential == "" { - return errors.New("authentication failed: k8s token is missing") - } - tokenReview := &kube_auth.TokenReview{ - Spec: kube_auth.TokenReviewSpec{ - Token: string(credential), - }, - } - if err := k.client.Create(ctx, tokenReview); err != nil { - return errors.Wrap(err, "authentication failed: call to TokenReview API failed") - } - if !tokenReview.Status.Authenticated { - return errors.Errorf("authentication failed: token doesn't belong to a valid user") - } - userInfo := strings.Split(tokenReview.Status.User.Username, ":") - if len(userInfo) != 4 { - return errors.Errorf("authentication failed: username inside TokenReview response has unexpected format: %q", tokenReview.Status.User.Username) - } - if !(userInfo[0] == "system" && userInfo[1] == "serviceaccount") { - return errors.Errorf("authentication failed: token must belong to a k8s system account, got %q", tokenReview.Status.User.Username) - } - _, proxyNamespace, err := util_k8s.CoreNameToK8sName(proxyId.Name) - if err != nil { - return err - } - namespace := userInfo[2] - if namespace != proxyNamespace { - return errors.Errorf("authentication failed: token belongs to a namespace (%q) different from proxyId (%q)", namespace, proxyNamespace) - } - return nil -} diff --git a/pkg/sds/auth/universal/auth_test.go b/pkg/sds/auth/universal/auth_test.go deleted file mode 100644 index 4114e2b4ccd9..000000000000 --- a/pkg/sds/auth/universal/auth_test.go +++ /dev/null @@ -1,226 +0,0 @@ -package universal_test - -import ( - "context" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - - "github.com/kumahq/kuma/api/mesh/v1alpha1" - core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" - "github.com/kumahq/kuma/pkg/core/resources/manager" - "github.com/kumahq/kuma/pkg/core/resources/store" - "github.com/kumahq/kuma/pkg/core/xds" - "github.com/kumahq/kuma/pkg/plugins/resources/memory" - "github.com/kumahq/kuma/pkg/sds/auth" - "github.com/kumahq/kuma/pkg/sds/auth/universal" - "github.com/kumahq/kuma/pkg/sds/server" - builtin_issuer "github.com/kumahq/kuma/pkg/tokens/builtin/issuer" -) - -var _ = Describe("Authentication flow", func() { - var privateKey = []byte("testPrivateKey") - - issuer := builtin_issuer.NewDataplaneTokenIssuer(func() ([]byte, error) { - return privateKey, nil - }) - var authenticator auth.Authenticator - var resStore store.ResourceStore - - BeforeEach(func() { - resStore = memory.NewStore() - authenticator = universal.NewAuthenticator( - issuer, - server.DefaultDataplaneResolver(manager.NewResourceManager(resStore)), - ) - - dpRes := core_mesh.DataplaneResource{ - Spec: v1alpha1.Dataplane{ - Networking: &v1alpha1.Dataplane_Networking{ - Address: "127.0.0.1", - Inbound: []*v1alpha1.Dataplane_Networking_Inbound{ - { - Port: 8080, - ServicePort: 8081, - Tags: map[string]string{ - "kuma.io/service": "web", - "kuma.io/protocol": "http", - }, - }, - { - Port: 8090, - ServicePort: 8091, - Tags: map[string]string{ - "kuma.io/service": "web-api", - "kuma.io/protocol": "http", - }, - }, - }, - }, - }, - } - err := resStore.Create(context.Background(), &dpRes, store.CreateByKey("dp-1", "default")) - Expect(err).ToNot(HaveOccurred()) - }) - - DescribeTable("should correctly authenticate dataplane", - func(id builtin_issuer.DataplaneIdentity) { - // when - credential, err := issuer.Generate(id) - - // then - Expect(err).ToNot(HaveOccurred()) - - // when - authIdentity, err := authenticator.Authenticate(context.Background(), xds.ProxyId{ - Mesh: "default", - Name: "dp-1", - }, auth.Credential(credential)) - - // then - Expect(err).ToNot(HaveOccurred()) - Expect(authIdentity.Services[0]).To(Equal("web")) - Expect(authIdentity.Mesh).To(Equal("default")) - }, - Entry("should auth with token bound to nothing", builtin_issuer.DataplaneIdentity{ - Name: "", - Mesh: "", - Tags: nil, - }), - Entry("should auth with token bound to mesh", builtin_issuer.DataplaneIdentity{ - Mesh: "default", - }), - Entry("should auth with token bound to mesh and name", builtin_issuer.DataplaneIdentity{ - Name: "dp-1", - Mesh: "default", - }), - Entry("should auth with token bound to mesh and tags", builtin_issuer.DataplaneIdentity{ - Mesh: "default", - Tags: map[string]map[string]bool{ - "kuma.io/service": { - "web": true, - "web-api": true, - }, - }, - }), - ) - - type testCase struct { - id builtin_issuer.DataplaneIdentity - err string - } - DescribeTable("should fail auth", - func(given testCase) { - // when - token, err := issuer.Generate(given.id) - - // then - Expect(err).ToNot(HaveOccurred()) - - // when - authId := xds.ProxyId{ - Mesh: "default", - Name: "dp-1", - } - _, err = authenticator.Authenticate(context.Background(), authId, auth.Credential(token)) - - // then - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(given.err)) - }, - Entry("on token with different name", testCase{ - id: builtin_issuer.DataplaneIdentity{ - Mesh: "default", - Name: "dp-2", - }, - err: "proxy name from requestor: dp-1 is different than in token: dp-2", - }), - Entry("on token with different mesh", testCase{ - id: builtin_issuer.DataplaneIdentity{ - Mesh: "demo", - Name: "dp-1", - }, - err: "proxy mesh from requestor: default is different than in token: demo", - }), - Entry("on token with different tags", testCase{ - id: builtin_issuer.DataplaneIdentity{ - Tags: map[string]map[string]bool{ - "kuma.io/service": { - "backend": true, - }, - }, - }, - err: `which is not allowed with this token. Allowed values in token are ["backend"]`, - }), - Entry("on token with tag that is absent in dataplane", testCase{ - id: builtin_issuer.DataplaneIdentity{ - Tags: map[string]map[string]bool{ - "kuma.io/zone": { - "east": true, - }, - }, - }, - err: `dataplane has no tag "kuma.io/zone" required by the token`, - }), - Entry("on token with missing one tag value", testCase{ - id: builtin_issuer.DataplaneIdentity{ - Tags: map[string]map[string]bool{ - "kuma.io/service": { - "web": true, - // "web-api": true valid token should have also web-api - }, - }, - }, - err: `which is not allowed with this token. Allowed values in token are ["web"]`, // web and web-api order is not stable - }), - ) - - It("should throw an error on invalid token", func() { - // when - id := xds.ProxyId{ - Mesh: "default", - Name: "dp-1", - } - _, err := authenticator.Authenticate(context.Background(), id, "this-is-not-valid-jwt-token") - - // then - Expect(err).To(MatchError("could not parse token: token contains an invalid number of segments")) - }) - - It("should throw an error when dataplane is not present in CP", func() { - // given - id := builtin_issuer.DataplaneIdentity{ - Mesh: "default", - Name: "non-existent-dp", - } - - // when - token, err := issuer.Generate(id) - - // then - Expect(err).ToNot(HaveOccurred()) - - // when - _, err = authenticator.Authenticate(context.Background(), xds.ProxyId{ - Mesh: "default", - Name: "non-existent-dp", - }, auth.Credential(token)) - - // then - Expect(err).To(MatchError(`unable to find Dataplane for proxy "default.non-existent-dp": Resource not found: type="Dataplane" name="non-existent-dp" mesh="default"`)) - }) - - It("should throw an error when signing key is not found", func() { - // given - issuer := builtin_issuer.NewDataplaneTokenIssuer(func() ([]byte, error) { - return nil, nil - }) - - // when - _, err := issuer.Generate(builtin_issuer.DataplaneIdentity{}) - - // then - Expect(err).To(MatchError("there is no Signing Key in the Control Plane. If you run multi-zone setup, make sure Remote is connected to the Global before generating tokens.")) - }) -}) diff --git a/pkg/sds/auth/universal/authenticator.go b/pkg/sds/auth/universal/authenticator.go deleted file mode 100644 index 8fa212477496..000000000000 --- a/pkg/sds/auth/universal/authenticator.go +++ /dev/null @@ -1,85 +0,0 @@ -package universal - -import ( - "context" - - "github.com/pkg/errors" - - "github.com/kumahq/kuma/api/mesh/v1alpha1" - "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" - core_xds "github.com/kumahq/kuma/pkg/core/xds" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" - common_auth "github.com/kumahq/kuma/pkg/sds/auth/common" - builtin_issuer "github.com/kumahq/kuma/pkg/tokens/builtin/issuer" -) - -func NewAuthenticator(issuer builtin_issuer.DataplaneTokenIssuer, dataplaneResolver common_auth.DataplaneResolver) sds_auth.Authenticator { - return &universalAuthenticator{ - issuer: issuer, - dataplaneResolver: dataplaneResolver, - } -} - -// universalAuthenticator defines authentication for Dataplane Tokens -// All fields in token are optional, so we only validate data that is available in token. This way you can pick your level of security. -// Generate token for mesh+name for maximum security. -// Generate token for mesh+tags(ex. kuma.io/service) so you can reuse the token for many instances of the same service. -// Generate token for mesh if you trust the scope of the mesh. -// -// If you generate token bound to tags all tags values have to match the dataplane, so for example if you have a Dataplane -// with inbounds: 1) kuma.io/service:web 2) kuma.io/service:web-api, you need token for both values kuma.io/service=web,web-api -// Dataplane also needs to have all tags defined in the token -type universalAuthenticator struct { - issuer builtin_issuer.DataplaneTokenIssuer - dataplaneResolver common_auth.DataplaneResolver -} - -func (u *universalAuthenticator) Authenticate(ctx context.Context, proxyId core_xds.ProxyId, credential sds_auth.Credential) (sds_auth.Identity, error) { - dataplane, err := u.dataplaneResolver(ctx, proxyId) - if err != nil { - return sds_auth.Identity{}, errors.Wrapf(err, "unable to find Dataplane for proxy %q", proxyId) - } - if err := u.reviewToken(dataplane, credential); err != nil { - return sds_auth.Identity{}, err - } - return common_auth.GetDataplaneIdentity(dataplane) -} - -func (u *universalAuthenticator) reviewToken(dataplane *mesh.DataplaneResource, credential sds_auth.Credential) error { - dpIdentity, err := u.issuer.Validate(builtin_issuer.Token(credential)) - if err != nil { - return err - } - - if dpIdentity.Name != "" { - if dataplane.Meta.GetName() != dpIdentity.Name { - return errors.Errorf("proxy name from requestor: %s is different than in token: %s", dataplane.Meta.GetName(), dpIdentity.Name) - } - } - if dpIdentity.Mesh != "" { - if dataplane.Meta.GetMesh() != dpIdentity.Mesh { - return errors.Errorf("proxy mesh from requestor: %s is different than in token: %s", dataplane.Meta.GetMesh(), dpIdentity.Mesh) - } - } - if err := validateTags(dpIdentity.Tags, dataplane.Spec.TagSet()); err != nil { - return err - } - return nil -} - -func validateTags(tokenTags v1alpha1.MultiValueTagSet, dpTags v1alpha1.MultiValueTagSet) error { - if len(tokenTags) != 0 { - for tagName, allowedValues := range tokenTags { - dpValues, exist := dpTags[tagName] - if !exist { - return errors.Errorf("dataplane has no tag %q required by the token", tagName) - } - for value := range dpValues { - if !allowedValues[value] { - return errors.Errorf("dataplane contains tag %q with value %q which is not allowed with this token. Allowed values in token are %q", tagName, value, tokenTags.Values(tagName)) - } - } - } - } - return nil -} diff --git a/pkg/sds/auth/universal/noop_authenticator.go b/pkg/sds/auth/universal/noop_authenticator.go deleted file mode 100644 index b2bddf9ae051..000000000000 --- a/pkg/sds/auth/universal/noop_authenticator.go +++ /dev/null @@ -1,29 +0,0 @@ -package universal - -import ( - "context" - - "github.com/pkg/errors" - - core_xds "github.com/kumahq/kuma/pkg/core/xds" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" - common_auth "github.com/kumahq/kuma/pkg/sds/auth/common" -) - -func NewNoopAuthenticator(dataplaneResolver common_auth.DataplaneResolver) sds_auth.Authenticator { - return &noopAuthenticator{ - dataplaneResolver: dataplaneResolver, - } -} - -type noopAuthenticator struct { - dataplaneResolver common_auth.DataplaneResolver -} - -func (u *noopAuthenticator) Authenticate(ctx context.Context, proxyId core_xds.ProxyId, _ sds_auth.Credential) (sds_auth.Identity, error) { - dataplane, err := u.dataplaneResolver(ctx, proxyId) - if err != nil { - return sds_auth.Identity{}, errors.Wrapf(err, "unable to find Dataplane for proxy %q", proxyId) - } - return common_auth.GetDataplaneIdentity(dataplane) -} diff --git a/pkg/sds/auth/universal/noop_authenticator_test.go b/pkg/sds/auth/universal/noop_authenticator_test.go deleted file mode 100644 index cb7374c72749..000000000000 --- a/pkg/sds/auth/universal/noop_authenticator_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package universal_test - -import ( - "context" - - mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1" - core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" - "github.com/kumahq/kuma/pkg/core/resources/manager" - "github.com/kumahq/kuma/pkg/core/resources/store" - "github.com/kumahq/kuma/pkg/core/xds" - "github.com/kumahq/kuma/pkg/plugins/resources/memory" - "github.com/kumahq/kuma/pkg/sds/auth" - "github.com/kumahq/kuma/pkg/sds/auth/universal" - "github.com/kumahq/kuma/pkg/sds/server" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("Noop Authenticator", func() { - - var authenticator auth.Authenticator - var resStore store.ResourceStore - - BeforeEach(func() { - resStore = memory.NewStore() - authenticator = universal.NewNoopAuthenticator(server.DefaultDataplaneResolver(manager.NewResourceManager(resStore))) - }) - - It("should allow with any token for existing dataplane", func() { - // given - id := xds.ProxyId{ - Mesh: "example", - Name: "dp-1", - } - - dpRes := core_mesh.DataplaneResource{ - Spec: mesh_proto.Dataplane{ - Networking: &mesh_proto.Dataplane_Networking{ - Address: "127.0.0.1", - Inbound: []*mesh_proto.Dataplane_Networking_Inbound{ - { - Port: 8080, - ServicePort: 8081, - Tags: map[string]string{ - "kuma.io/service": "web", - }, - }, - }, - }, - }, - } - err := resStore.Create(context.Background(), &dpRes, store.CreateBy(id.ToResourceKey())) - Expect(err).ToNot(HaveOccurred()) - - // when - authIdentity, err := authenticator.Authenticate(context.Background(), id, "some-random-token") - - // then - Expect(err).ToNot(HaveOccurred()) - Expect(authIdentity.Services[0]).To(Equal("web")) - Expect(authIdentity.Mesh).To(Equal(id.Mesh)) - }) - - It("should throw an error when dataplane is not present in CP", func() { - // given - id := xds.ProxyId{ - Mesh: "example", - Name: "dp-1", - } - - // when - _, err := authenticator.Authenticate(context.Background(), id, "some-random-token") - - // then - Expect(err).To(HaveOccurred()) - Expect(err).To(MatchError(`unable to find Dataplane for proxy "example.dp-1": Resource not found: type="Dataplane" name="dp-1" mesh="example"`)) - }) -}) diff --git a/pkg/sds/auth/universal/universal_suite_test.go b/pkg/sds/auth/universal/universal_suite_test.go deleted file mode 100644 index 61cb867511e5..000000000000 --- a/pkg/sds/auth/universal/universal_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package universal_test - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestUniversal(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Sds Auth Universal Suite") -} diff --git a/pkg/sds/provider/ca/provider.go b/pkg/sds/provider/ca/provider.go index 03220e3f6e4d..e88f539df28d 100644 --- a/pkg/sds/provider/ca/provider.go +++ b/pkg/sds/provider/ca/provider.go @@ -9,7 +9,6 @@ import ( core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" core_manager "github.com/kumahq/kuma/pkg/core/resources/manager" core_store "github.com/kumahq/kuma/pkg/core/resources/store" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" sds_provider "github.com/kumahq/kuma/pkg/sds/provider" ) @@ -29,7 +28,7 @@ func (s *meshCaProvider) RequiresIdentity() bool { return false } -func (s *meshCaProvider) Get(ctx context.Context, resource string, requestor sds_auth.Identity) (sds_provider.Secret, error) { +func (s *meshCaProvider) Get(ctx context.Context, resource string, requestor sds_provider.Identity) (sds_provider.Secret, error) { meshName := requestor.Mesh meshRes := &core_mesh.MeshResource{} diff --git a/pkg/sds/provider/identity/provider.go b/pkg/sds/provider/identity/provider.go index e98824b22b45..0530261475cb 100644 --- a/pkg/sds/provider/identity/provider.go +++ b/pkg/sds/provider/identity/provider.go @@ -9,7 +9,6 @@ import ( core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" core_manager "github.com/kumahq/kuma/pkg/core/resources/manager" core_store "github.com/kumahq/kuma/pkg/core/resources/store" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" sds_provider "github.com/kumahq/kuma/pkg/sds/provider" ) @@ -29,7 +28,7 @@ func (s *identityCertProvider) RequiresIdentity() bool { return true } -func (s *identityCertProvider) Get(ctx context.Context, name string, requestor sds_auth.Identity) (sds_provider.Secret, error) { +func (s *identityCertProvider) Get(ctx context.Context, name string, requestor sds_provider.Identity) (sds_provider.Secret, error) { meshName := requestor.Mesh meshRes := &core_mesh.MeshResource{} diff --git a/pkg/sds/provider/interfaces.go b/pkg/sds/provider/interfaces.go index dd474d9b758a..6326fd5d8208 100644 --- a/pkg/sds/provider/interfaces.go +++ b/pkg/sds/provider/interfaces.go @@ -4,15 +4,19 @@ import ( "context" envoy_auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth" - - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" ) type Secret interface { ToResource(name string) *envoy_auth.Secret } +type Identity struct { + Mesh string + Name string + Services []string +} + type SecretProvider interface { RequiresIdentity() bool - Get(ctx context.Context, name string, requestor sds_auth.Identity) (Secret, error) + Get(ctx context.Context, name string, requestor Identity) (Secret, error) } diff --git a/pkg/sds/server/auth_callbacks.go b/pkg/sds/server/auth_callbacks.go deleted file mode 100644 index 5e33190d9aa5..000000000000 --- a/pkg/sds/server/auth_callbacks.go +++ /dev/null @@ -1,92 +0,0 @@ -package server - -import ( - "context" - "sync" - - envoy_api "github.com/envoyproxy/go-control-plane/envoy/api/v2" - envoy_server "github.com/envoyproxy/go-control-plane/pkg/server/v2" - "github.com/pkg/errors" - - core_xds "github.com/kumahq/kuma/pkg/core/xds" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" -) - -func newAuthCallbacks(authenticator sds_auth.Authenticator) envoy_server.Callbacks { - return &authCallbacks{ - authenticator: authenticator, - contexts: map[core_xds.StreamID]context.Context{}, - } -} - -// authCallback checks if the DiscoveryRequest is authorized, ie. if it has a valid Dataplane Token/Service Account Token. -type authCallbacks struct { - sync.RWMutex - authenticator sds_auth.Authenticator - contexts map[core_xds.StreamID]context.Context -} - -var _ envoy_server.Callbacks = &authCallbacks{} - -func (a *authCallbacks) OnStreamOpen(ctx context.Context, streamID core_xds.StreamID, s string) error { - a.Lock() - defer a.Unlock() - - a.contexts[streamID] = ctx - return nil -} - -func (a *authCallbacks) OnStreamClosed(streamID core_xds.StreamID) { -} - -func (a *authCallbacks) OnStreamRequest(streamID core_xds.StreamID, req *envoy_api.DiscoveryRequest) error { - credential, err := a.credential(streamID) - if err != nil { - return err - } - return a.authenticate(credential, req) -} - -func (a *authCallbacks) credential(streamID core_xds.StreamID) (sds_auth.Credential, error) { - a.RLock() - defer a.RUnlock() - - ctx, exists := a.contexts[streamID] - if !exists { - return "", errors.Errorf("there is no context for stream ID %d", streamID) - } - credential, err := sds_auth.ExtractCredential(ctx) - if err != nil { - return "", err - } - return credential, err -} - -func (a *authCallbacks) authenticate(credential sds_auth.Credential, req *envoy_api.DiscoveryRequest) error { - proxyId, err := core_xds.ParseProxyId(req.Node) - if err != nil { - return errors.Wrap(err, "SDS request must have a valid Proxy Id") - } - - _, err = a.authenticator.Authenticate(context.Background(), *proxyId, credential) - if err != nil { - return errors.Wrap(err, "authentication failed") - } - return nil -} - -func (a *authCallbacks) OnStreamResponse(core_xds.StreamID, *envoy_api.DiscoveryRequest, *envoy_api.DiscoveryResponse) { -} - -func (a *authCallbacks) OnFetchRequest(ctx context.Context, request *envoy_api.DiscoveryRequest) error { - credential, err := sds_auth.ExtractCredential(ctx) - if err != nil { - return err - } - return a.authenticate(credential, request) -} - -func (a *authCallbacks) OnFetchResponse(*envoy_api.DiscoveryRequest, *envoy_api.DiscoveryResponse) { -} - -var _ envoy_server.Callbacks = &authCallbacks{} diff --git a/pkg/sds/server/components.go b/pkg/sds/server/components.go index a8c9c8900ad0..3e1b8356dd36 100644 --- a/pkg/sds/server/components.go +++ b/pkg/sds/server/components.go @@ -1,76 +1,12 @@ package server import ( - "context" - - "github.com/pkg/errors" - kube_auth "k8s.io/api/authentication/v1" - - config_core "github.com/kumahq/kuma/pkg/config/core" - core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" - core_manager "github.com/kumahq/kuma/pkg/core/resources/manager" - core_store "github.com/kumahq/kuma/pkg/core/resources/store" core_runtime "github.com/kumahq/kuma/pkg/core/runtime" - core_xds "github.com/kumahq/kuma/pkg/core/xds" - k8s_runtime "github.com/kumahq/kuma/pkg/runtime/k8s" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" - k8s_sds_auth "github.com/kumahq/kuma/pkg/sds/auth/k8s" - universal_sds_auth "github.com/kumahq/kuma/pkg/sds/auth/universal" sds_provider "github.com/kumahq/kuma/pkg/sds/provider" ca_sds_provider "github.com/kumahq/kuma/pkg/sds/provider/ca" identity_sds_provider "github.com/kumahq/kuma/pkg/sds/provider/identity" - "github.com/kumahq/kuma/pkg/tokens/builtin" -) - -const ( - MeshCaResource = "mesh_ca" - IdentityCertResource = "identity_cert" ) -func NewKubeAuthenticator(rt core_runtime.Runtime) (sds_auth.Authenticator, error) { - mgr, ok := k8s_runtime.FromManagerContext(rt.Extensions()) - if !ok { - return nil, errors.Errorf("k8s controller runtime Manager hasn't been configured") - } - if err := kube_auth.AddToScheme(mgr.GetScheme()); err != nil { - return nil, errors.Wrapf(err, "could not add %q to scheme", kube_auth.SchemeGroupVersion) - } - return k8s_sds_auth.New(mgr.GetClient(), DefaultDataplaneResolver(rt.ResourceManager())), nil -} - -func NewUniversalAuthenticator(rt core_runtime.Runtime) (sds_auth.Authenticator, error) { - dpResolver := DefaultDataplaneResolver(rt.ResourceManager()) - if !rt.Config().AdminServer.Apis.DataplaneToken.Enabled { - return universal_sds_auth.NewNoopAuthenticator(dpResolver), nil - } - issuer, err := builtin.NewDataplaneTokenIssuer(rt) - if err != nil { - return nil, err - } - return universal_sds_auth.NewAuthenticator(issuer, dpResolver), nil -} - -func DefaultAuthenticator(rt core_runtime.Runtime) (sds_auth.Authenticator, error) { - switch env := rt.Config().Environment; env { - case config_core.KubernetesEnvironment: - return NewKubeAuthenticator(rt) - case config_core.UniversalEnvironment: - return NewUniversalAuthenticator(rt) - default: - return nil, errors.Errorf("unable to choose SDS authenticator for environment type %q", env) - } -} - -func DefaultDataplaneResolver(resourceManager core_manager.ResourceManager) func(context.Context, core_xds.ProxyId) (*core_mesh.DataplaneResource, error) { - return func(ctx context.Context, proxyId core_xds.ProxyId) (*core_mesh.DataplaneResource, error) { - dataplane := &core_mesh.DataplaneResource{} - if err := resourceManager.Get(ctx, dataplane, core_store.GetBy(proxyId.ToResourceKey())); err != nil { - return nil, err - } - return dataplane, nil - } -} - func DefaultMeshCaProvider(rt core_runtime.Runtime) sds_provider.SecretProvider { return ca_sds_provider.New(rt.ResourceManager(), rt.CaManagers()) } diff --git a/pkg/sds/server/reconciler.go b/pkg/sds/server/reconciler.go index a54ca27f1c44..c1876bc3877e 100644 --- a/pkg/sds/server/reconciler.go +++ b/pkg/sds/server/reconciler.go @@ -26,8 +26,8 @@ import ( core_model "github.com/kumahq/kuma/pkg/core/resources/model" core_store "github.com/kumahq/kuma/pkg/core/resources/store" core_xds "github.com/kumahq/kuma/pkg/core/xds" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" sds_provider "github.com/kumahq/kuma/pkg/sds/provider" + "github.com/kumahq/kuma/pkg/xds/envoy" ) // DataplaneReconciler keeps the state of the Cache for SDS consistent @@ -139,19 +139,19 @@ func (d *DataplaneReconciler) shouldGenerateSnapshot(proxyID string, mesh *mesh_ } func (d *DataplaneReconciler) generateSnapshot(dataplane *mesh_core.DataplaneResource, mesh *mesh_core.MeshResource) (envoy_cache.Snapshot, error) { - requestor := sds_auth.Identity{ + requestor := sds_provider.Identity{ Services: dataplane.Spec.TagSet().Values(mesh_proto.ServiceTag), Mesh: dataplane.GetMeta().GetMesh(), } - identitySecret, err := d.identityProvider.Get(context.Background(), IdentityCertResource, requestor) + identitySecret, err := d.identityProvider.Get(context.Background(), envoy.IdentityCertResource, requestor) if err != nil { return envoy_cache.Snapshot{}, errors.Wrap(err, "could not get Dataplane cert pair") } - requestor = sds_auth.Identity{ + requestor = sds_provider.Identity{ Mesh: dataplane.GetMeta().GetMesh(), } - caSecret, err := d.meshCaProvider.Get(context.Background(), MeshCaResource, requestor) + caSecret, err := d.meshCaProvider.Get(context.Background(), envoy.MeshCaResource, requestor) if err != nil { return envoy_cache.Snapshot{}, errors.Wrap(err, "could not get mesh CA cert") } @@ -161,14 +161,14 @@ func (d *DataplaneReconciler) generateSnapshot(dataplane *mesh_core.DataplaneRes Resources: [envoy_types.UnknownType]envoy_cache.Resources{}, } snap.Resources[envoy_types.Secret] = envoy_cache.NewResources(version, []envoy_types.Resource{ - identitySecret.ToResource(IdentityCertResource), - caSecret.ToResource(MeshCaResource), + identitySecret.ToResource(envoy.IdentityCertResource), + caSecret.ToResource(envoy.MeshCaResource), }) return snap, nil } func (d *DataplaneReconciler) updateInsights(dataplaneId core_model.ResourceKey, snapshot envoy_cache.Snapshot) error { - secret := snapshot.Resources[envoy_types.Secret].Items[IdentityCertResource].(*envoy_auth.Secret) + secret := snapshot.Resources[envoy_types.Secret].Items[envoy.IdentityCertResource].(*envoy_auth.Secret) certPEM := secret.GetTlsCertificate().CertificateChain.GetInlineBytes() block, _ := pem.Decode(certPEM) cert, err := x509.ParseCertificate(block.Bytes) diff --git a/pkg/sds/server/server.go b/pkg/sds/server/server.go index 9f161b2267b7..ae7b87550e9a 100644 --- a/pkg/sds/server/server.go +++ b/pkg/sds/server/server.go @@ -17,6 +17,8 @@ import ( core_metrics "github.com/kumahq/kuma/pkg/metrics" util_watchdog "github.com/kumahq/kuma/pkg/util/watchdog" util_xds "github.com/kumahq/kuma/pkg/util/xds" + xds_auth "github.com/kumahq/kuma/pkg/xds/auth" + xds_server "github.com/kumahq/kuma/pkg/xds/server" xds_sync "github.com/kumahq/kuma/pkg/xds/sync" ) @@ -31,11 +33,11 @@ func SetupServer(rt core_runtime.Runtime) error { caProvider := DefaultMeshCaProvider(rt) identityProvider := DefaultIdentityCertProvider(rt) - authenticator, err := DefaultAuthenticator(rt) + authenticator, err := xds_server.DefaultAuthenticator(rt) if err != nil { return err } - authCallbacks := newAuthCallbacks(authenticator) + authCallbacks := xds_auth.NewCallbacks(rt.ResourceManager(), authenticator) reconciler := DataplaneReconciler{ resManager: rt.ResourceManager(), diff --git a/pkg/sds/server/server_test.go b/pkg/sds/server/server_test.go index 0c809550c81d..e33f920c0ac8 100644 --- a/pkg/sds/server/server_test.go +++ b/pkg/sds/server/server_test.go @@ -20,13 +20,13 @@ import ( core_manager "github.com/kumahq/kuma/pkg/core/resources/manager" core_store "github.com/kumahq/kuma/pkg/core/resources/store" core_metrics "github.com/kumahq/kuma/pkg/metrics" - sds_auth "github.com/kumahq/kuma/pkg/sds/auth" "github.com/kumahq/kuma/pkg/sds/server" "github.com/kumahq/kuma/pkg/test" test_metrics "github.com/kumahq/kuma/pkg/test/metrics" "github.com/kumahq/kuma/pkg/test/runtime" tokens_builtin "github.com/kumahq/kuma/pkg/tokens/builtin" tokens_issuer "github.com/kumahq/kuma/pkg/tokens/builtin/issuer" + "github.com/kumahq/kuma/pkg/xds/envoy" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -34,7 +34,7 @@ import ( var _ = Describe("SDS Server", func() { - var dpCredential sds_auth.Credential + var dpCredential tokens_issuer.Token var stop chan struct{} var client envoy_discovery.SecretDiscoveryServiceClient var conn *grpc.ClientConn @@ -120,12 +120,11 @@ var _ = Describe("SDS Server", func() { // retrieve example DP token tokenIssuer, err := tokens_builtin.NewDataplaneTokenIssuer(runtime) Expect(err).ToNot(HaveOccurred()) - token, err := tokenIssuer.Generate(tokens_issuer.DataplaneIdentity{ + dpCredential, err = tokenIssuer.Generate(tokens_issuer.DataplaneIdentity{ Name: dpRes.GetMeta().GetName(), Mesh: dpRes.GetMeta().GetMesh(), }) Expect(err).ToNot(HaveOccurred()) - dpCredential = sds_auth.Credential(token) // start the runtime Expect(server.SetupServer(runtime)).To(Succeed()) @@ -157,14 +156,14 @@ var _ = Describe("SDS Server", func() { Node: &envoy_api_core.Node{ Id: "default.backend-01", }, - ResourceNames: []string{server.MeshCaResource, server.IdentityCertResource}, + ResourceNames: []string{envoy.MeshCaResource, envoy.IdentityCertResource}, TypeUrl: envoy_resource.SecretType, } } It("should return CA and Identity cert when DP is authorized", func(done Done) { // given - ctx := metadata.AppendToOutgoingContext(context.Background(), "authorization", string(dpCredential)) + ctx := metadata.AppendToOutgoingContext(context.Background(), "authorization", dpCredential) stream, err := client.StreamSecrets(ctx) defer func() { if stream != nil { @@ -206,7 +205,7 @@ var _ = Describe("SDS Server", func() { BeforeEach(func(done Done) { // given - ctx := metadata.AppendToOutgoingContext(context.Background(), "authorization", string(dpCredential)) + ctx := metadata.AppendToOutgoingContext(context.Background(), "authorization", dpCredential) s, err := client.StreamSecrets(ctx) stream = s Expect(err).ToNot(HaveOccurred()) diff --git a/pkg/tokens/builtin/issuer/issuer.go b/pkg/tokens/builtin/issuer/issuer.go index 9e0eb187172d..c13fe9f93eea 100644 --- a/pkg/tokens/builtin/issuer/issuer.go +++ b/pkg/tokens/builtin/issuer/issuer.go @@ -9,10 +9,18 @@ import ( type Token = string +type DpType = string + +const ( + DpTypeDataplane = "dataplane" + DpTypeIngress = "ingress" +) + type DataplaneIdentity struct { Name string Mesh string Tags mesh_proto.MultiValueTagSet + Type DpType } // DataplaneTokenIssuer issues Dataplane Tokens used then for proving identity of the dataplanes. @@ -27,6 +35,7 @@ type claims struct { Name string Mesh string Tags map[string][]string + Type string jwt.StandardClaims } @@ -68,6 +77,7 @@ func (i *jwtTokenIssuer) Generate(identity DataplaneIdentity) (Token, error) { Name: identity.Name, Mesh: identity.Mesh, Tags: tags, + Type: identity.Type, StandardClaims: jwt.StandardClaims{}, } @@ -101,6 +111,7 @@ func (i *jwtTokenIssuer) Validate(rawToken Token) (DataplaneIdentity, error) { Mesh: c.Mesh, Name: c.Name, Tags: mesh_proto.MultiValueTagSetFrom(c.Tags), + Type: c.Type, } return id, nil } diff --git a/pkg/tokens/builtin/server/types/dataplane_token_request.go b/pkg/tokens/builtin/server/types/dataplane_token_request.go index 576178819d63..f7f189a8ba92 100644 --- a/pkg/tokens/builtin/server/types/dataplane_token_request.go +++ b/pkg/tokens/builtin/server/types/dataplane_token_request.go @@ -4,4 +4,5 @@ type DataplaneTokenRequest struct { Name string `json:"name"` Mesh string `json:"mesh"` Tags map[string][]string `json:"tags"` + Type string `json:"type"` } diff --git a/pkg/tokens/builtin/server/webservice.go b/pkg/tokens/builtin/server/webservice.go index acf880b76fd2..353674faa21e 100644 --- a/pkg/tokens/builtin/server/webservice.go +++ b/pkg/tokens/builtin/server/webservice.go @@ -45,6 +45,7 @@ func (d *dataplaneTokenWebService) handleIdentityRequest(request *restful.Reques token, err := d.issuer.Generate(issuer.DataplaneIdentity{ Mesh: idReq.Mesh, Name: idReq.Name, + Type: idReq.Type, Tags: mesh_proto.MultiValueTagSetFrom(idReq.Tags), }) if err != nil { diff --git a/pkg/xds/auth/universal/auth_test.go b/pkg/xds/auth/universal/auth_test.go index 9f8e685af68b..85b44f0584e6 100644 --- a/pkg/xds/auth/universal/auth_test.go +++ b/pkg/xds/auth/universal/auth_test.go @@ -56,6 +56,23 @@ var _ = Describe("Authentication flow", func() { }, } + ingressDp := core_mesh.DataplaneResource{ + Spec: mesh_proto.Dataplane{ + Networking: &mesh_proto.Dataplane_Networking{ + Ingress: &mesh_proto.Dataplane_Networking_Ingress{}, + Inbound: []*mesh_proto.Dataplane_Networking_Inbound{ + { + Port: 8080, + ServicePort: 8081, + Tags: map[string]string{ + "kuma.io/service": "ingress", + }, + }, + }, + }, + }, + } + BeforeEach(func() { resStore = memory.NewStore() authenticator = universal.NewAuthenticator(issuer) @@ -64,47 +81,66 @@ var _ = Describe("Authentication flow", func() { Expect(err).ToNot(HaveOccurred()) }) + type testCase struct { + id builtin_issuer.DataplaneIdentity + dpRes *core_mesh.DataplaneResource + err string + } DescribeTable("should correctly authenticate dataplane", - func(id builtin_issuer.DataplaneIdentity) { + func(given testCase) { // when - credential, err := issuer.Generate(id) + credential, err := issuer.Generate(given.id) // then Expect(err).ToNot(HaveOccurred()) // when - err = authenticator.Authenticate(context.Background(), &dpRes, credential) + err = authenticator.Authenticate(context.Background(), given.dpRes, credential) // then Expect(err).ToNot(HaveOccurred()) }, - Entry("should auth with token bound to nothing", builtin_issuer.DataplaneIdentity{ - Name: "", - Mesh: "", - Tags: nil, + Entry("should auth with token bound to nothing", testCase{ + id: builtin_issuer.DataplaneIdentity{ + Name: "", + Mesh: "", + Tags: nil, + }, + dpRes: &dpRes, }), - Entry("should auth with token bound to mesh", builtin_issuer.DataplaneIdentity{ - Mesh: "default", + Entry("should auth with token bound to mesh", testCase{ + id: builtin_issuer.DataplaneIdentity{ + Mesh: "default", + }, + dpRes: &dpRes, }), - Entry("should auth with token bound to mesh and name", builtin_issuer.DataplaneIdentity{ - Name: "dp-1", - Mesh: "default", + Entry("should auth with token bound to mesh and name", testCase{ + id: builtin_issuer.DataplaneIdentity{ + Name: "dp-1", + Mesh: "default", + }, + dpRes: &dpRes, }), - Entry("should auth with token bound to mesh and tags", builtin_issuer.DataplaneIdentity{ - Mesh: "default", - Tags: map[string]map[string]bool{ - "kuma.io/service": { - "web": true, - "web-api": true, + Entry("should auth with token bound to mesh and tags", testCase{ + id: builtin_issuer.DataplaneIdentity{ + Mesh: "default", + Tags: map[string]map[string]bool{ + "kuma.io/service": { + "web": true, + "web-api": true, + }, }, }, + dpRes: &dpRes, + }), + Entry("should auth with ingress token", testCase{ + id: builtin_issuer.DataplaneIdentity{ + Type: builtin_issuer.DpTypeIngress, + }, + dpRes: &ingressDp, }), ) - type testCase struct { - id builtin_issuer.DataplaneIdentity - err string - } DescribeTable("should fail auth", func(given testCase) { // when @@ -114,7 +150,7 @@ var _ = Describe("Authentication flow", func() { Expect(err).ToNot(HaveOccurred()) // when - err = authenticator.Authenticate(context.Background(), &dpRes, token) + err = authenticator.Authenticate(context.Background(), given.dpRes, token) // then Expect(err).To(HaveOccurred()) @@ -125,14 +161,16 @@ var _ = Describe("Authentication flow", func() { Mesh: "default", Name: "dp-2", }, - err: "proxy name from requestor: dp-1 is different than in token: dp-2", + dpRes: &dpRes, + err: "proxy name from requestor: dp-1 is different than in token: dp-2", }), Entry("on token with different mesh", testCase{ id: builtin_issuer.DataplaneIdentity{ Mesh: "demo", Name: "dp-1", }, - err: "proxy mesh from requestor: default is different than in token: demo", + dpRes: &dpRes, + err: "proxy mesh from requestor: default is different than in token: demo", }), Entry("on token with different tags", testCase{ id: builtin_issuer.DataplaneIdentity{ @@ -142,7 +180,8 @@ var _ = Describe("Authentication flow", func() { }, }, }, - err: `which is not allowed with this token. Allowed values in token are ["backend"]`, + dpRes: &dpRes, + err: `which is not allowed with this token. Allowed values in token are ["backend"]`, }), Entry("on token with tag that is absent in dataplane", testCase{ id: builtin_issuer.DataplaneIdentity{ @@ -152,7 +191,8 @@ var _ = Describe("Authentication flow", func() { }, }, }, - err: `dataplane has no tag "kuma.io/zone" required by the token`, + dpRes: &dpRes, + err: `dataplane has no tag "kuma.io/zone" required by the token`, }), Entry("on token with missing one tag value", testCase{ id: builtin_issuer.DataplaneIdentity{ @@ -163,7 +203,27 @@ var _ = Describe("Authentication flow", func() { }, }, }, - err: `which is not allowed with this token. Allowed values in token are ["web"]`, // web and web-api order is not stable + dpRes: &dpRes, + err: `which is not allowed with this token. Allowed values in token are ["web"]`, // web and web-api order is not stable + }), + Entry("regular dataplane and ingress type", testCase{ + id: builtin_issuer.DataplaneIdentity{ + Type: builtin_issuer.DpTypeIngress, + }, + dpRes: &dpRes, + err: `dataplane is of type Dataplane but token allows only for the "ingress" type`, + }), + Entry("ingress dataplane and dataplane type", testCase{ + id: builtin_issuer.DataplaneIdentity{ + Type: builtin_issuer.DpTypeDataplane, + }, + dpRes: &ingressDp, + err: `dataplane is of type Ingress but token allows only for the "dataplane" type`, + }), + Entry("ingress dataplane and dataplane type (but not explicitly specified)", testCase{ + id: builtin_issuer.DataplaneIdentity{}, + dpRes: &ingressDp, + err: `dataplane is of type Ingress but token allows only for the "dataplane" type`, }), ) diff --git a/pkg/xds/auth/universal/authenticator.go b/pkg/xds/auth/universal/authenticator.go index b1d10c4e8c13..f24ef540ca1e 100644 --- a/pkg/xds/auth/universal/authenticator.go +++ b/pkg/xds/auth/universal/authenticator.go @@ -42,12 +42,28 @@ func (u *universalAuthenticator) Authenticate(ctx context.Context, dataplane *co if dpIdentity.Mesh != "" && dataplane.Meta.GetMesh() != dpIdentity.Mesh { return errors.Errorf("proxy mesh from requestor: %s is different than in token: %s", dataplane.Meta.GetMesh(), dpIdentity.Mesh) } + if err := validateType(dataplane, dpIdentity.Type); err != nil { + return err + } if err := validateTags(dpIdentity.Tags, dataplane.Spec.TagSet()); err != nil { return err } return nil } +func validateType(dataplane *core_mesh.DataplaneResource, dpType builtin_issuer.DpType) error { + if dpType == "" { // if dp type is not explicitly specified we assume it's dataplane so we force Ingress token + dpType = builtin_issuer.DpTypeDataplane + } + if dataplane.Spec.IsIngress() && dpType != builtin_issuer.DpTypeIngress { + return errors.Errorf("dataplane is of type Ingress but token allows only for the %q type", dpType) + } + if !dataplane.Spec.IsIngress() && dpType == builtin_issuer.DpTypeIngress { + return errors.Errorf("dataplane is of type Dataplane but token allows only for the %q type", dpType) + } + return nil +} + func validateTags(tokenTags mesh_proto.MultiValueTagSet, dpTags mesh_proto.MultiValueTagSet) error { for tagName, allowedValues := range tokenTags { dpValues, exist := dpTags[tagName] diff --git a/pkg/xds/envoy/tls.go b/pkg/xds/envoy/tls.go index f45d97d37c1a..f66f4e4ee5ee 100644 --- a/pkg/xds/envoy/tls.go +++ b/pkg/xds/envoy/tls.go @@ -10,12 +10,16 @@ import ( "github.com/golang/protobuf/ptypes/wrappers" core_xds "github.com/kumahq/kuma/pkg/core/xds" - "github.com/kumahq/kuma/pkg/sds/server" "github.com/kumahq/kuma/pkg/util/proto" util_xds "github.com/kumahq/kuma/pkg/util/xds" xds_context "github.com/kumahq/kuma/pkg/xds/context" ) +const ( + MeshCaResource = "mesh_ca" + IdentityCertResource = "identity_cert" +) + // CreateDownstreamTlsContext creates DownstreamTlsContext for incoming connections // It verifies that incoming connection has TLS certificate signed by Mesh CA with URI SAN of prefix spiffe://{mesh_name}/ // It secures inbound listener with certificate of "identity_cert" that will be received from the SDS (it contains URI SANs of all inbounds). @@ -63,11 +67,11 @@ func CreateUpstreamTlsContext(ctx xds_context.Context, metadata *core_xds.Datapl } func CreateCommonTlsContext(ctx xds_context.Context, metadata *core_xds.DataplaneMetadata, validationSANMatcher *envoy_type_matcher.StringMatcher) (*envoy_auth.CommonTlsContext, error) { - meshCaSecret, err := sdsSecretConfig(ctx, server.MeshCaResource, metadata) + meshCaSecret, err := sdsSecretConfig(ctx, MeshCaResource, metadata) if err != nil { return nil, err } - identitySecret, err := sdsSecretConfig(ctx, server.IdentityCertResource, metadata) + identitySecret, err := sdsSecretConfig(ctx, IdentityCertResource, metadata) if err != nil { return nil, err } diff --git a/test/framework/universal_controlplane.go b/test/framework/universal_controlplane.go index 34c06ce029e8..42dacc06d430 100644 --- a/test/framework/universal_controlplane.go +++ b/test/framework/universal_controlplane.go @@ -58,11 +58,15 @@ func (c *UniversalControlPlane) GetGlobaStatusAPI() string { } func (c *UniversalControlPlane) GenerateDpToken(service string) (string, error) { + dpType := "" + if service == "ingress" { + dpType = "ingress" + } return retry.DoWithRetryE(c.t, "generating DP token", DefaultRetries, DefaultTimeout, func() (string, error) { sshApp := NewSshApp(c.verbose, c.cluster.apps[AppModeCP].ports["22"], []string{}, []string{"curl", "--fail", "--show-error", "-H", "\"Content-Type: application/json\"", - "--data", fmt.Sprintf(`'{"mesh": "default", "tags": {"kuma.io/service":["%s"]}}'`, service), + "--data", fmt.Sprintf(`'{"mesh": "default", "type": "%s", "tags": {"kuma.io/service":["%s"]}}'`, dpType, service), "http://localhost:5679/tokens"}) if err := sshApp.Run(); err != nil { return "", err