diff --git a/common/auth.go b/common/auth.go index a4bdecbf..fcfa9d72 100644 --- a/common/auth.go +++ b/common/auth.go @@ -47,6 +47,8 @@ type ( Type string // Name is the entity name Name string + // Groups contains the groups the entity belongs to + Groups []string } // Operation is type for an user operation, e.g. CreateDestination, PublishDestination diff --git a/common/auth_util.go b/common/auth_util.go index 693c084b..0c215927 100644 --- a/common/auth_util.go +++ b/common/auth_util.go @@ -26,12 +26,46 @@ import ( ) const ( - resourceURNTemplateCreateDestination = "urn:cherami:dst:%v" + resourceURNTemplateCreateDestination = "urn:cherami:dst:%v:%v" + resourceURNTemplateCreateConsumerGroup = "urn:cherami:dst:%v:%v" ) -// GetResourceRootURN returns the root resource URN, e.g. urn:cherami:dst:zone1_prod +// GetResourceURNCreateDestination returns the resource URN to create destination, e.g. urn:cherami:dst:zone1_prod:/prefix1 // We use URN (Uniform Resource Name) like this: https://www.ietf.org/rfc/rfc2141.txt -func GetResourceRootURN(scommon SCommon) string { +func GetResourceURNCreateDestination(scommon SCommon, dstPath *string) string { + var dstPathString string + if dstPath == nil { + dstPathString = "" + } else { + dstPathString = getPathRootName(dstPath) + } deploymentName := scommon.GetConfig().GetDeploymentName() - return fmt.Sprintf(resourceURNTemplateCreateDestination, strings.ToLower(deploymentName)) + return fmt.Sprintf(resourceURNTemplateCreateDestination, strings.ToLower(deploymentName), strings.ToLower(dstPathString)) +} + +// GetResourceURNCreateConsumerGroup returns the resource URN to create consumer group, e.g. urn:cherami:dst:zone1_prod:/dst1 +// We use URN (Uniform Resource Name) like this: https://www.ietf.org/rfc/rfc2141.txt +func GetResourceURNCreateConsumerGroup(scommon SCommon, dstPath *string) string { + var dstPathString string + if dstPath == nil { + dstPathString = "" + } else { + dstPathString = *dstPath + } + deploymentName := scommon.GetConfig().GetDeploymentName() + return fmt.Sprintf(resourceURNTemplateCreateConsumerGroup, strings.ToLower(deploymentName), strings.ToLower(dstPathString)) +} + +func getPathRootName(path *string) string { + if path == nil || *path == "" { + return "" + } + + parts := strings.Split(*path, "/") + + if strings.HasPrefix(*path, "/") { + return "/" + parts[1] + } + + return parts[0] } diff --git a/common/auth_util_test.go b/common/auth_util_test.go new file mode 100644 index 00000000..2125cb7f --- /dev/null +++ b/common/auth_util_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package common + +import ( + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/uber/cherami-server/common/configure" + "testing" +) + +type AuthUtilSuite struct { + *require.Assertions // override suite.Suite.Assertions with require.Assertions; this means that s.NotNil(nil) will stop the test, not merely log an error + suite.Suite +} + +type serviceConfig struct { + configure.ServiceConfig + deploymentName string +} + +func (r *serviceConfig) GetDeploymentName() string { + return r.deploymentName +} + +func TestAuthUtilSuite(t *testing.T) { + suite.Run(t, new(AuthUtilSuite)) +} + +func (s *AuthUtilSuite) SetupTest() { + s.Assertions = require.New(s.T()) // Have to define our overridden assertions in the test setup. If we did it earlier, s.T() will return nil +} + +func (s *AuthUtilSuite) TestGetResourceURNCreateDestination() { + mockService := new(MockService) + + config := &serviceConfig{} + + mockService.On("GetConfig").Return(config) + + s.Equal("urn:cherami:dst::", GetResourceURNCreateDestination(mockService, nil)) + s.Equal("urn:cherami:dst::", GetResourceURNCreateDestination(mockService, StringPtr(""))) + + config.deploymentName = "zone1" + s.Equal("urn:cherami:dst:zone1:", GetResourceURNCreateDestination(mockService, nil)) + s.Equal("urn:cherami:dst:zone1:", GetResourceURNCreateDestination(mockService, StringPtr(""))) + s.Equal("urn:cherami:dst:zone1:/", GetResourceURNCreateDestination(mockService, StringPtr("/"))) + s.Equal("urn:cherami:dst:zone1:/", GetResourceURNCreateDestination(mockService, StringPtr("//"))) + + config.deploymentName = "Zone2_ABC" + s.Equal("urn:cherami:dst:zone2_abc:/dst1", GetResourceURNCreateDestination(mockService, StringPtr("/Dst1"))) + s.Equal("urn:cherami:dst:zone2_abc:/root2", GetResourceURNCreateDestination(mockService, StringPtr("/Root2/Dst2"))) + + s.Equal("urn:cherami:dst:zone2_abc:dst2", GetResourceURNCreateDestination(mockService, StringPtr("Dst2"))) + s.Equal("urn:cherami:dst:zone2_abc:root2", GetResourceURNCreateDestination(mockService, StringPtr("Root2/Dst2"))) +} + +func (s *AuthUtilSuite) TestGetResourceURNCreateConsumerGroup() { + mockService := new(MockService) + + config := &serviceConfig{} + + mockService.On("GetConfig").Return(config) + + s.Equal("urn:cherami:dst::", GetResourceURNCreateConsumerGroup(mockService, nil)) + s.Equal("urn:cherami:dst::", GetResourceURNCreateConsumerGroup(mockService, StringPtr(""))) + + config.deploymentName = "zone1" + s.Equal("urn:cherami:dst:zone1:", GetResourceURNCreateConsumerGroup(mockService, nil)) + s.Equal("urn:cherami:dst:zone1:", GetResourceURNCreateConsumerGroup(mockService, StringPtr(""))) + s.Equal("urn:cherami:dst:zone1:/", GetResourceURNCreateConsumerGroup(mockService, StringPtr("/"))) + s.Equal("urn:cherami:dst:zone1://", GetResourceURNCreateConsumerGroup(mockService, StringPtr("//"))) + + config.deploymentName = "Zone2_ABC" + s.Equal("urn:cherami:dst:zone2_abc:/dst1", GetResourceURNCreateConsumerGroup(mockService, StringPtr("/Dst1"))) + s.Equal("urn:cherami:dst:zone2_abc:/root2/dst2", GetResourceURNCreateConsumerGroup(mockService, StringPtr("/Root2/Dst2"))) + + s.Equal("urn:cherami:dst:zone2_abc:dst2", GetResourceURNCreateConsumerGroup(mockService, StringPtr("Dst2"))) + s.Equal("urn:cherami:dst:zone2_abc:root2/dst2", GetResourceURNCreateConsumerGroup(mockService, StringPtr("Root2/Dst2"))) +} diff --git a/services/frontendhost/frontend.go b/services/frontendhost/frontend.go index 6510dfeb..d1dd77b9 100644 --- a/services/frontendhost/frontend.go +++ b/services/frontendhost/frontend.go @@ -595,7 +595,7 @@ func (h *Frontend) CreateDestination(ctx thrift.Context, createRequest *c.Create return nil, err } - authResource := common.GetResourceRootURN(h.SCommon) + authResource := common.GetResourceURNCreateDestination(h.SCommon, createRequest.Path) err = h.GetAuthManager().Authorize(authSubject, common.OperationCreate, common.Resource(authResource)) if err != nil { lclLg.WithField(common.TagSubject, authSubject).WithField(common.TagResource, authResource).Info("Not allowed to create destination") @@ -1101,6 +1101,20 @@ func (h *Frontend) CreateConsumerGroup(ctx thrift.Context, createRequest *c.Crea common.TagCnsPth: common.FmtCnsPth(createRequest.GetConsumerGroupName()), }) + authSubject, err := h.GetAuthManager().Authenticate(ctx) + if err != nil { + // TODO add metrics + return nil, err + } + + authResource := common.GetResourceURNCreateConsumerGroup(h.SCommon, createRequest.DestinationPath) + err = h.GetAuthManager().Authorize(authSubject, common.OperationRead, common.Resource(authResource)) + if err != nil { + lclLg.WithField(common.TagSubject, authSubject).WithField(common.TagResource, authResource).Info("Not allowed to create consumer group") + // TODO add metrics + return nil, err + } + // request to controller var cClient controller.TChanController cClient, err = h.getControllerClient() diff --git a/tools/admin/lib.go b/tools/admin/lib.go index cf904386..dfe19e3e 100644 --- a/tools/admin/lib.go +++ b/tools/admin/lib.go @@ -31,6 +31,7 @@ import ( "time" "github.com/codegangsta/cli" + cherami2 "github.com/uber/cherami-client-go/client/cherami" mcli "github.com/uber/cherami-server/clients/metadata" "github.com/uber/cherami-server/common" toolscommon "github.com/uber/cherami-server/tools/common" @@ -45,7 +46,12 @@ const ( // CreateDestination creates a destination func CreateDestination(c *cli.Context, cliHelper common.CliHelper) { - cClient := toolscommon.GetCClient(c, adminToolService) + CreateDestinationSecure(c, cliHelper, nil) +} + +// CreateDestinationSecure creates a destination with security enabled +func CreateDestinationSecure(c *cli.Context, cliHelper common.CliHelper, authProvider cherami2.AuthProvider) { + cClient := toolscommon.GetCClientSecure(c, adminToolService, authProvider) toolscommon.CreateDestination(c, cClient, cliHelper) } @@ -58,7 +64,12 @@ func UpdateDestination(c *cli.Context) { // CreateConsumerGroup creates a consumer group func CreateConsumerGroup(c *cli.Context, cliHelper common.CliHelper) { - cClient := toolscommon.GetCClient(c, adminToolService) + CreateConsumerGroupSecure(c, cliHelper, nil) +} + +// CreateConsumerGroupSecure creates a consumer group with security enabled +func CreateConsumerGroupSecure(c *cli.Context, cliHelper common.CliHelper, authProvider cherami2.AuthProvider) { + cClient := toolscommon.GetCClientSecure(c, adminToolService, authProvider) mClient := toolscommon.GetMClient(c, adminToolService) toolscommon.CreateConsumerGroup(c, cClient, mClient, cliHelper) } diff --git a/tools/cli/lib.go b/tools/cli/lib.go index ff9a4033..d28688e6 100644 --- a/tools/cli/lib.go +++ b/tools/cli/lib.go @@ -22,6 +22,7 @@ package cli import ( "github.com/codegangsta/cli" + cherami2 "github.com/uber/cherami-client-go/client/cherami" scommon "github.com/uber/cherami-server/common" "github.com/uber/cherami-server/tools/common" ) @@ -38,7 +39,12 @@ func ReadCgBacklog(c *cli.Context) { // CreateDestination creates a destination func CreateDestination(c *cli.Context, cliHelper scommon.CliHelper) { - cClient := common.GetCClient(c, serviceName) + CreateDestinationSecure(c, cliHelper, nil) +} + +// CreateDestinationSecure creates a destination with security enabled +func CreateDestinationSecure(c *cli.Context, cliHelper scommon.CliHelper, authProvider cherami2.AuthProvider) { + cClient := common.GetCClientSecure(c, serviceName, authProvider) common.CreateDestination(c, cClient, cliHelper) } @@ -51,8 +57,13 @@ func UpdateDestination(c *cli.Context) { // CreateConsumerGroup creates the CG func CreateConsumerGroup(c *cli.Context, cliHelper scommon.CliHelper) { + CreateConsumerGroupSecure(c, cliHelper, nil) +} + +// CreateConsumerGroupSecure creates the CG with security enabled +func CreateConsumerGroupSecure(c *cli.Context, cliHelper scommon.CliHelper, authProvider cherami2.AuthProvider) { mClient := common.GetMClient(c, serviceName) - cClient := common.GetCClient(c, serviceName) + cClient := common.GetCClientSecure(c, serviceName, authProvider) common.CreateConsumerGroup(c, cClient, mClient, cliHelper) } diff --git a/tools/common/lib.go b/tools/common/lib.go index 0f180eeb..a6300f6c 100644 --- a/tools/common/lib.go +++ b/tools/common/lib.go @@ -134,12 +134,18 @@ func newGlobalOptionsFromCLIContext(c *cli.Context) *GlobalOptions { // GetCClient return a cherami.Client func GetCClient(c *cli.Context, serviceName string) ccli.Client { + return GetCClientSecure(c, serviceName, nil) +} + +// GetCClientSecure return a cherami.Client with security enabled +func GetCClientSecure(c *cli.Context, serviceName string, authProvider ccli.AuthProvider) ccli.Client { gOpts := newGlobalOptionsFromCLIContext(c) var cClient ccli.Client var err error cOpts := ccli.ClientOptions{ Timeout: time.Duration(gOpts.timeoutSecs) * time.Second, DeploymentStr: gOpts.env, + AuthProvider: authProvider, } if !(len(gOpts.frontendHost) > 0 || gOpts.frontendPort > 0) && gOpts.hyperbahn {