Skip to content

Commit

Permalink
Add OPA policies for NSM registries (#1334)
Browse files Browse the repository at this point in the history
* add auth registy chain element

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* add unit test + add test policy

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* fix test policy and policy input

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* rework spiffieIDNSEMap

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* rename and move nse register validation policy to opa folder

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* add register and unregister policies to authorizeNSEServer

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* cleanup

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* add policy for NSE unregister case + cleanup

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* start adding ns_server

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* finish ns_server

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* cleanup

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* fix linter

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* fix linter

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* fix tests

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* fix tests

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* add StringSet type

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* fix linter

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* fix linter

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* cleanup

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* add authorize registry servers to nsmgr opts

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* add default policies to opa registry

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* use the same registry for register and unregister cases

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* fix linter

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* run goimports

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

* fix linter issue

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>

Signed-off-by: Nikita Skrynnik <nikita.skrynnik@xored.com>
  • Loading branch information
NikitaSkrynnik committed Sep 13, 2022
1 parent daf1d3a commit a546f3c
Show file tree
Hide file tree
Showing 21 changed files with 826 additions and 48 deletions.
27 changes: 27 additions & 0 deletions pkg/networkservice/chains/nsmgr/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"github.com/networkservicemesh/sdk/pkg/networkservice/common/metrics"
"github.com/networkservicemesh/sdk/pkg/networkservice/core/adapters"
"github.com/networkservicemesh/sdk/pkg/registry"
registryauthorize "github.com/networkservicemesh/sdk/pkg/registry/common/authorize"
"github.com/networkservicemesh/sdk/pkg/registry/common/begin"
"github.com/networkservicemesh/sdk/pkg/registry/common/clientconn"
registryclientinfo "github.com/networkservicemesh/sdk/pkg/registry/common/clientinfo"
Expand Down Expand Up @@ -77,6 +78,8 @@ type nsmgrServer struct {
type serverOptions struct {
authorizeServer networkservice.NetworkServiceServer
authorizeMonitorConnectionServer networkservice.MonitorConnectionServer
authorizeNSRegistryServer registryapi.NetworkServiceRegistryServer
authorizeNSERegistryServer registryapi.NetworkServiceEndpointRegistryServer
dialOptions []grpc.DialOption
dialTimeout time.Duration
regURL *url.URL
Expand Down Expand Up @@ -130,6 +133,26 @@ func WithAuthorizeMonitorConnectionServer(authorizeMonitorConnectionServer netwo
}
}

// WithAuthorizeNSRegistryServer sets authorization NetworkServiceRegistry chain element
func WithAuthorizeNSRegistryServer(authorizeNSRegistryServer registryapi.NetworkServiceRegistryServer) Option {
if authorizeNSRegistryServer == nil {
panic("authorizeNSRegistryServer cannot be nil")
}
return func(o *serverOptions) {
o.authorizeNSRegistryServer = authorizeNSRegistryServer
}
}

// WithAuthorizeNSERegistryServer sets authorization NetworkServiceEndpointRegistry chain element
func WithAuthorizeNSERegistryServer(authorizeNSERegistryServer registryapi.NetworkServiceEndpointRegistryServer) Option {
if authorizeNSERegistryServer == nil {
panic("authorizeNSERegistryServer cannot be nil")
}
return func(o *serverOptions) {
o.authorizeNSERegistryServer = authorizeNSERegistryServer
}
}

// WithRegistry sets URL and dial options to reach the upstream registry, if not passed memory storage will be used.
func WithRegistry(regURL *url.URL) Option {
return func(o *serverOptions) {
Expand Down Expand Up @@ -162,6 +185,8 @@ func NewServer(ctx context.Context, tokenGenerator token.GeneratorFunc, options
opts := &serverOptions{
authorizeServer: authorize.NewServer(authorize.Any()),
authorizeMonitorConnectionServer: authmonitor.NewMonitorConnectionServer(authmonitor.Any()),
authorizeNSRegistryServer: registryauthorize.NewNetworkServiceRegistryServer(registryauthorize.Any()),
authorizeNSERegistryServer: registryauthorize.NewNetworkServiceEndpointRegistryServer(registryauthorize.Any()),
name: "nsmgr-" + uuid.New().String(),
forwarderServiceName: "forwarder",
}
Expand All @@ -187,6 +212,7 @@ func NewServer(ctx context.Context, tokenGenerator token.GeneratorFunc, options
}

nsRegistry = chain.NewNetworkServiceRegistryServer(
opts.authorizeNSRegistryServer,
nsRegistry,
)

Expand Down Expand Up @@ -214,6 +240,7 @@ func NewServer(ctx context.Context, tokenGenerator token.GeneratorFunc, options

var nseRegistry = chain.NewNetworkServiceEndpointRegistryServer(
begin.NewNetworkServiceEndpointRegistryServer(),
opts.authorizeNSERegistryServer,
registryclientinfo.NewNetworkServiceEndpointRegistryServer(),
expire.NewNetworkServiceEndpointRegistryServer(ctx, time.Minute),
registryrecvfd.NewNetworkServiceEndpointRegistryServer(), // Allow to receive a passed files
Expand Down
3 changes: 2 additions & 1 deletion pkg/networkservice/common/authorize/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/networkservicemesh/sdk/pkg/networkservice/core/next"
"github.com/networkservicemesh/sdk/pkg/tools/opa"
"github.com/networkservicemesh/sdk/pkg/tools/spire"
"github.com/networkservicemesh/sdk/pkg/tools/stringset"
)

type authorizeServer struct {
Expand Down Expand Up @@ -76,7 +77,7 @@ func (a *authorizeServer) Request(ctx context.Context, request *networkservice.N
connID := conn.GetPath().GetPathSegments()[index-1].GetId()
ids, ok := a.spiffeIDConnectionMap.Load(spiffeID)
if !ok {
ids = &spire.ConnectionIDSet{}
ids = new(stringset.StringSet)
}
ids.Store(connID, struct{}{})
a.spiffeIDConnectionMap.Store(spiffeID, ids)
Expand Down
70 changes: 70 additions & 0 deletions pkg/registry/common/authorize/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) 2022 Cisco and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package authorize

import (
"context"

"github.com/spiffe/go-spiffe/v2/spiffeid"

"github.com/networkservicemesh/sdk/pkg/tools/stringset"
)

// RegistryOpaInput represents input for policies in authorizNSEServer and authorizeNSServer
type RegistryOpaInput struct {
SpiffeID string `json:"spiffe_id"`
ResourceName string `json:"resource_name"`
SpiffeIDResourcesMap map[string][]string `json:"spiffe_id_resources_map"`
}

// Policy represents authorization policy for network service.
type Policy interface {
// Check checks authorization
Check(ctx context.Context, input interface{}) error
}

type policiesList []Policy

func (l *policiesList) check(ctx context.Context, input RegistryOpaInput) error {
if l == nil {
return nil
}
for _, policy := range *l {
if policy == nil {
continue
}
if err := policy.Check(ctx, input); err != nil {
return err
}
}
return nil
}

func getRawMap(m *spiffeIDResourcesMap) map[string][]string {
rawMap := make(map[string][]string)
m.Range(func(key spiffeid.ID, value *stringset.StringSet) bool {
id := key.String()
rawMap[id] = make([]string, 0)
value.Range(func(key string, value struct{}) bool {
rawMap[id] = append(rawMap[id], key)
return true
})
return true
})

return rawMap
}
61 changes: 61 additions & 0 deletions pkg/registry/common/authorize/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2022 Cisco and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package authorize_test

import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"math/big"
"net/url"
"time"

"google.golang.org/grpc/credentials"
"google.golang.org/grpc/peer"
)

func generateCert(u *url.URL) []byte {
ca := &x509.Certificate{
SerialNumber: big.NewInt(1653),
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
URIs: []*url.URL{u},
}

priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
pub := &priv.PublicKey

certBytes, _ := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv)
return certBytes
}

func withPeer(ctx context.Context, certBytes []byte) (context.Context, error) {
x509cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}

authInfo := &credentials.TLSInfo{
State: tls.ConnectionState{
PeerCertificates: []*x509.Certificate{x509cert},
},
}
return peer.NewContext(ctx, &peer.Peer{AuthInfo: authInfo}), nil
}
26 changes: 26 additions & 0 deletions pkg/registry/common/authorize/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2022 Cisco and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package authorize

import (
"sync"
)

//go:generate go-syncmap -output sync_map.gen.go -type spiffeIDResourcesMap<github.com/spiffe/go-spiffe/v2/spiffeid.ID,*github.com/networkservicemesh/sdk/pkg/tools/stringset.StringSet>

// spiffeIDResourcesMap - sync.Map with key == spiffeID and value == list of NSEs associated with spiffeID
type spiffeIDResourcesMap sync.Map
119 changes: 119 additions & 0 deletions pkg/registry/common/authorize/ns_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (c) 2022 Cisco and/or its affiliates.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package authorize provides authz checks for incoming or returning connections.
package authorize

import (
"context"

"github.com/golang/protobuf/ptypes/empty"

"github.com/networkservicemesh/api/pkg/api/registry"

"github.com/networkservicemesh/sdk/pkg/registry/core/next"
"github.com/networkservicemesh/sdk/pkg/tools/opa"
"github.com/networkservicemesh/sdk/pkg/tools/spire"
"github.com/networkservicemesh/sdk/pkg/tools/stringset"
)

type authorizeNSServer struct {
policies policiesList
spiffeIDNSsMap *spiffeIDResourcesMap
}

// NewNetworkServiceRegistryServer - returns a new authorization registry.NetworkServiceRegistryServer
// Authorize registry server checks spiffeID of NS.
func NewNetworkServiceRegistryServer(opts ...Option) registry.NetworkServiceRegistryServer {
o := &options{
policies: policiesList{opa.WithRegistryClientAllowedPolicy()},
spiffeIDResourcesMap: new(spiffeIDResourcesMap),
}

for _, opt := range opts {
opt(o)
}

return &authorizeNSServer{
policies: o.policies,
spiffeIDNSsMap: o.spiffeIDResourcesMap,
}
}

func (s *authorizeNSServer) Register(ctx context.Context, ns *registry.NetworkService) (*registry.NetworkService, error) {
spiffeID, err := spire.SpiffeIDFromContext(ctx)
if err != nil && len(s.policies) == 0 {
return next.NetworkServiceRegistryServer(ctx).Register(ctx, ns)
}

rawMap := getRawMap(s.spiffeIDNSsMap)
input := RegistryOpaInput{
SpiffeID: spiffeID.String(),
ResourceName: ns.Name,
SpiffeIDResourcesMap: rawMap,
}
if err := s.policies.check(ctx, input); err != nil {
return nil, err
}

nsNames, ok := s.spiffeIDNSsMap.Load(spiffeID)
if !ok {
nsNames = new(stringset.StringSet)
}
nsNames.Store(ns.Name, struct{}{})
s.spiffeIDNSsMap.Store(spiffeID, nsNames)

return next.NetworkServiceRegistryServer(ctx).Register(ctx, ns)
}

func (s *authorizeNSServer) Find(query *registry.NetworkServiceQuery, server registry.NetworkServiceRegistry_FindServer) error {
return next.NetworkServiceRegistryServer(server.Context()).Find(query, server)
}

func (s *authorizeNSServer) Unregister(ctx context.Context, ns *registry.NetworkService) (*empty.Empty, error) {
spiffeID, err := spire.SpiffeIDFromContext(ctx)
if err != nil && len(s.policies) == 0 {
return next.NetworkServiceRegistryServer(ctx).Unregister(ctx, ns)
}

rawMap := getRawMap(s.spiffeIDNSsMap)
input := RegistryOpaInput{
SpiffeID: spiffeID.String(),
ResourceName: ns.Name,
SpiffeIDResourcesMap: rawMap,
}
if err := s.policies.check(ctx, input); err != nil {
return nil, err
}

nsNames, ok := s.spiffeIDNSsMap.Load(spiffeID)
if ok {
nsNames.Delete(ns.Name)
namesEmpty := true
nsNames.Range(func(key string, value struct{}) bool {
namesEmpty = false
return true
})

if namesEmpty {
s.spiffeIDNSsMap.Delete(spiffeID)
} else {
s.spiffeIDNSsMap.Store(spiffeID, nsNames)
}
}

return next.NetworkServiceRegistryServer(ctx).Unregister(ctx, ns)
}

0 comments on commit a546f3c

Please sign in to comment.