Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provider plugin poc #22

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions core/provider/client_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package provider

import (
"fmt"

"github.com/goto/guardian/domain"
"github.com/goto/guardian/plugins/providers/newpoc"
)

// TODO: move this to guardian/plugins/providers
type pluginFactory struct {
clients map[string]clientV2
configs map[string]providerConfig
}

func (f *pluginFactory) getConfig(pc *domain.ProviderConfig) (providerConfig, error) {
if f.configs == nil {
f.configs = make(map[string]providerConfig)
}

key := pc.URN
if config, ok := f.configs[key]; ok {
return config, nil
}

switch pc.Type {
case newpoc.ProviderType:
config, err := newpoc.NewConfig(pc)
if err != nil {
return nil, err
}
f.configs[key] = config
return config, nil
default:
return nil, fmt.Errorf("unknown provider type: %q", pc.Type)
}
}

func (f *pluginFactory) getClient(cfg providerConfig) (clientV2, error) {
if f.clients == nil {
f.clients = make(map[string]clientV2)
}

key := cfg.GetProviderConfig().URN
if client, ok := f.clients[key]; ok {
return client, nil
}

providerType := cfg.GetProviderConfig().Type
switch providerType {
case newpoc.ProviderType:
client, err := newpoc.NewClient(cfg.(*newpoc.Config))
if err != nil {
return nil, err
}
f.clients[key] = client
return client, nil
default:
return nil, fmt.Errorf("unknown provider type: %q", providerType)
}
}
2 changes: 2 additions & 0 deletions core/provider/common.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package provider

// TODO: remove this file

import (
"context"
"fmt"
Expand Down
168 changes: 168 additions & 0 deletions core/provider/plugin_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package provider

import (
"context"
"fmt"

"github.com/go-playground/validator/v10"
"github.com/goto/guardian/domain"
"github.com/goto/guardian/plugins/providers/newpoc"
)

type pluginAdapter struct {
providerType string
allowedAccountTypes []string
factory *pluginFactory

validator *validator.Validate
crypto domain.Crypto
}

func (a *pluginAdapter) GetType() string {
return a.providerType
}

func (a *pluginAdapter) CreateConfig(pc *domain.ProviderConfig) error {
config, err := a.factory.getConfig(pc)
if err != nil {
return fmt.Errorf("initializing config for %q: %w", pc.Type, err)
}

if err := config.Validate(context.TODO(), a.validator); err != nil {
return fmt.Errorf("invalid config: %w", err)
}

if encryptableConfig, ok := config.(encryptable); ok {
if err := encryptableConfig.Encrypt(a.crypto); err != nil {
return fmt.Errorf("encrypting config: %w", err)
}
}

return nil
}

func (a *pluginAdapter) GetResources(pc *domain.ProviderConfig) ([]*domain.Resource, error) {
config, err := a.factory.getConfig(pc)
if err != nil {
return nil, fmt.Errorf("initializing config for %q: %w", pc.URN, err)
}
client, err := a.factory.getClient(config)
if err != nil {
return nil, fmt.Errorf("initializing client for %q: %w", pc.URN, err)
}

resourceables, err := client.ListResources(context.TODO())
if err != nil {
return nil, fmt.Errorf("listing resources for %q: %w", pc.URN, err)
}

resources := make([]*domain.Resource, 0, len(resourceables))
for _, resourceable := range resourceables {
r := &domain.Resource{
ProviderType: pc.Type,
ProviderURN: pc.URN,
Type: resourceable.GetType(),
URN: resourceable.GetURN(),
Name: resourceable.GetDisplayName(),
}
if md := resourceable.GetMetadata(); md != nil {
r.Details = map[string]interface{}{
"__metadata": resourceable.GetMetadata(),
}
}
resources = append(resources, r)
}

return resources, nil
}

func (a *pluginAdapter) GrantAccess(pc *domain.ProviderConfig, grant domain.Grant) error {
config, err := a.factory.getConfig(pc)
if err != nil {
return fmt.Errorf("initializing config for %q: %w", pc.URN, err)
}
client, err := a.factory.getClient(config)
if err != nil {
return fmt.Errorf("initializing client for %q: %w", pc.URN, err)
}

return client.GrantAccess(context.TODO(), grant)
}

func (a *pluginAdapter) RevokeAccess(pc *domain.ProviderConfig, grant domain.Grant) error {
config, err := a.factory.getConfig(pc)
if err != nil {
return fmt.Errorf("initializing config for %q: %w", pc.URN, err)
}
client, err := a.factory.getClient(config)
if err != nil {
return fmt.Errorf("initializing client for %q: %w", pc.URN, err)
}

return client.RevokeAccess(context.TODO(), grant)
}

func (a *pluginAdapter) GetRoles(pc *domain.ProviderConfig, resourceType string) ([]*domain.Role, error) {
for _, r := range pc.Resources {
if r.Type == resourceType {
return r.Roles, nil
}
}

return nil, ErrInvalidResourceType
}

func (a *pluginAdapter) GetAccountTypes() []string {
return a.allowedAccountTypes
}

func (a *pluginAdapter) ListAccess(ctx context.Context, pc domain.ProviderConfig, resources []*domain.Resource) (domain.MapResourceAccess, error) {
config, err := a.factory.getConfig(&pc)
if err != nil {
return nil, fmt.Errorf("initializing config for %q: %w", pc.URN, err)
}
client, err := a.factory.getClient(config)
if err != nil {
return nil, fmt.Errorf("initializing client for %q: %w", pc.URN, err)
}

if accessImporter, ok := client.(AccessImporter); ok {
return accessImporter.ListAccess(ctx, pc, resources)
}

return nil, fmt.Errorf("ListAccess %w", ErrUnimplementedMethod)
}

func (a *pluginAdapter) GetPermissions(pc *domain.ProviderConfig, resourceType, role string) ([]interface{}, error) {
for _, rc := range pc.Resources {
if rc.Type != resourceType {
continue
}
for _, r := range rc.Roles {
if r.ID == role {
if r.Permissions == nil {
return make([]interface{}, 0), nil
}
return r.Permissions, nil
}
}
return nil, ErrInvalidRole
}
return nil, ErrInvalidResourceType
}

func getNewPlugins(pluginFactory *pluginFactory, validator *validator.Validate, crypto domain.Crypto) map[string]Client {
return map[string]Client{
newpoc.ProviderType: &pluginAdapter{
providerType: newpoc.ProviderType,
allowedAccountTypes: []string{
newpoc.AccountTypeUser,
newpoc.AccountTypeGroup,
newpoc.AccountTypeServiceAccount,
},
factory: pluginFactory,
validator: validator,
crypto: crypto,
},
}
}
35 changes: 35 additions & 0 deletions core/provider/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const (
ReservedDetailsKeyPolicyQuestions = "__policy_questions"
)

var (
migratedPluginTypes = map[string]bool{
"newpoc": true,
}
)

//go:generate mockery --name=repository --exported --with-expecter
type repository interface {
Create(context.Context, *domain.Provider) error
Expand All @@ -37,6 +43,29 @@ type repository interface {
Delete(ctx context.Context, id string) error
}

type providerConfig interface {
GetProviderConfig() *domain.ProviderConfig
Validate(ctx context.Context, validator *validator.Validate) error
}

type encryptable interface {
Encrypt(encryptor domain.Encryptor) error
}

type decryptable interface {
Decrypt(decryptor domain.Decryptor) error
}

type clientV2 interface {
ListResources(context.Context) ([]domain.Resourceable, error)
GrantAccess(context.Context, domain.Grant) error
RevokeAccess(context.Context, domain.Grant) error
}

type AccessImporter interface {
ListAccess(context.Context, domain.ProviderConfig, []*domain.Resource) (domain.MapResourceAccess, error)
}

//go:generate mockery --name=Client --exported --with-expecter
type Client interface {
providers.PermissionManager
Expand Down Expand Up @@ -79,14 +108,20 @@ type ServiceDeps struct {
Validator *validator.Validate
Logger log.Logger
AuditLogger auditLogger
Crypto domain.Crypto
}

// NewService returns service struct
func NewService(deps ServiceDeps) *Service {
pluginFactory := &pluginFactory{}

mapProviderClients := make(map[string]Client)
for _, c := range deps.Clients {
mapProviderClients[c.GetType()] = c
}
for providerType, adaptedPlugin := range getNewPlugins(pluginFactory, deps.Validator, deps.Crypto) {
mapProviderClients[providerType] = adaptedPlugin
}

return &Service{
deps.Repository,
Expand Down
8 changes: 8 additions & 0 deletions domain/provider.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package domain

import (
"context"
"fmt"
"sort"
"time"

"github.com/go-playground/validator/v10"
)

const (
Expand Down Expand Up @@ -105,3 +108,8 @@ type ProviderType struct {
Name string `json:"name" yaml:"name"`
ResourceTypes []string `json:"resource_types" yaml:"resource_types"`
}

type ProviderConfigurable interface {
GetProvider() *Provider
Validate(context.Context, *validator.Validate) error
}
11 changes: 11 additions & 0 deletions domain/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ package domain

import "time"

type Resourceable interface {
GetType() string
GetURN() string
GetDisplayName() string
GetMetadata() map[string]interface{}
}

// Resource struct
type Resource struct {
ID string `json:"id" yaml:"id"`
Expand All @@ -19,6 +26,10 @@ type Resource struct {
Children []*Resource `json:"children,omitempty" yaml:"children,omitempty"`
}

func (r *Resource) GetType() string {
return r.Type
}

func (r *Resource) GetFlattened() []*Resource {
resources := []*Resource{r}
for _, child := range r.Children {
Expand Down
1 change: 1 addition & 0 deletions internal/server/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ func InitServices(deps ServiceDeps) (*Services, error) {
Validator: deps.Validator,
Logger: deps.Logger,
AuditLogger: auditLogger,
Crypto: deps.Crypto,
})
activityService := activity.NewService(activity.ServiceDeps{
Repository: activityRepository,
Expand Down
15 changes: 15 additions & 0 deletions pkg/option/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package option

import "github.com/go-playground/validator/v10"

type options struct {
validator *validator.Validate
}

type Option func(*options)

func WithValidator(validator *validator.Validate) Option {
return func(opts *options) {
opts.validator = validator
}
}
Loading