diff --git a/api/types/plugin.go b/api/types/plugin.go index 9c7b6278c3562..8321850c2fe48 100644 --- a/api/types/plugin.go +++ b/api/types/plugin.go @@ -23,6 +23,16 @@ import ( "github.com/gravitational/trace" ) +// PluginType represents the type of the plugin +type PluginType string + +const ( + // PluginTypeUnknown is returned when no plugin type matches. + PluginTypeUnknown PluginType = "" + // PluginTypeSlack is the Slack access request plugin + PluginTypeSlack = "slack" +) + // Plugin represents a plugin instance type Plugin interface { // ResourceWithSecrets provides common resource methods. @@ -30,6 +40,7 @@ type Plugin interface { Clone() Plugin GetCredentials() PluginCredentials GetStatus() PluginStatus + GetType() PluginType SetCredentials(PluginCredentials) error SetStatus(PluginStatus) error } @@ -214,6 +225,16 @@ func (p *PluginV1) SetStatus(status PluginStatus) error { return nil } +// GetType implements Plugin +func (p *PluginV1) GetType() PluginType { + switch p.Spec.Settings.(type) { + case *PluginSpecV1_SlackAccessPlugin: + return PluginTypeSlack + default: + return PluginTypeUnknown + } +} + // CheckAndSetDefaults validates and set the default values func (s *PluginSlackAccessSettings) CheckAndSetDefaults() error { if s.FallbackChannel == "" { diff --git a/e_imports.go b/e_imports.go index 3ee905e1fdf34..031d7de134591 100644 --- a/e_imports.go +++ b/e_imports.go @@ -49,6 +49,8 @@ import ( _ "github.com/go-piv/piv-go/piv" _ "github.com/google/go-attestation/attest" _ "github.com/gravitational/form" + _ "github.com/gravitational/teleport-plugins/access/common" + _ "github.com/gravitational/teleport-plugins/access/slack" _ "google.golang.org/api/admin/directory/v1" _ "google.golang.org/api/cloudidentity/v1" _ "google.golang.org/genproto/googleapis/rpc/status" diff --git a/go.mod b/go.mod index c9e597eb0088c..7a7038c712deb 100644 --- a/go.mod +++ b/go.mod @@ -236,6 +236,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-resty/resty/v2 v2.3.0 // indirect github.com/go-webauthn/revoke v0.1.6 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect @@ -304,6 +305,7 @@ require ( github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nsf/termbox-go v1.1.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/pelletier/go-toml v1.8.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pingcap/errors v0.11.5-0.20201126102027-b0a155152ca3 // indirect diff --git a/go.sum b/go.sum index b24da1bd20644..3783eb966c835 100644 --- a/go.sum +++ b/go.sum @@ -119,6 +119,7 @@ github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzS github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0/go.mod h1:BDJ5qMFKx9DugEg3+uQSDCdbYPr5s9vBTrL9P8TpqOU= github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 h1:oPdPEZFSbl7oSPEAIPMPBMUmiL+mqgzBJwM/9qYcwNg= github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= @@ -453,6 +454,8 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-piv/piv-go v1.10.0 h1:P1Y1VjBI5DnXW0+YkKmTuh5opWnMIrKriUaIOblee9Q= github.com/go-piv/piv-go v1.10.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= +github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= @@ -1024,6 +1027,7 @@ github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.1/go.mod h1:lAVhWwbNaveeJmxrxuST github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= diff --git a/lib/web/ui/usercontext.go b/lib/web/ui/usercontext.go index 8b9c9a1582112..db67f8c88f0b7 100644 --- a/lib/web/ui/usercontext.go +++ b/lib/web/ui/usercontext.go @@ -93,6 +93,8 @@ type userACL struct { Download access `json:"download"` // Download defines whether the user has access to download the license License access `json:"license"` + // Plugins defines whether the user has access to manage hosted plugin instances + Plugins access `json:"plugins"` } type authType string @@ -191,6 +193,11 @@ func NewUserContext(user types.User, userRoles services.RoleSet, features proto. billingAccess = newAccess(userRoles, ctx, types.KindBilling) } + var pluginsAccess access + if features.Plugins { + pluginsAccess = newAccess(userRoles, ctx, types.KindPlugin) + } + accessStrategy := getAccessStrategy(userRoles) clipboard := userRoles.DesktopClipboard() desktopSessionRecording := desktopRecordingEnabled && userRoles.RecordDesktopSession() @@ -221,6 +228,7 @@ func NewUserContext(user types.User, userRoles services.RoleSet, features proto. DirectorySharing: directorySharing, Download: download, License: license, + Plugins: pluginsAccess, } // local user diff --git a/web/packages/teleport/src/mocks/contexts.ts b/web/packages/teleport/src/mocks/contexts.ts index 3026779ee3d95..2f6e6415c131f 100644 --- a/web/packages/teleport/src/mocks/contexts.ts +++ b/web/packages/teleport/src/mocks/contexts.ts @@ -50,6 +50,7 @@ export const fullAcl: Acl = { directorySharingEnabled: true, license: fullAccess, download: fullAccess, + plugins: fullAccess, }; export const userContext = makeUserContext({ diff --git a/web/packages/teleport/src/services/user/makeAcl.ts b/web/packages/teleport/src/services/user/makeAcl.ts index 6e05f17b6bf42..c2e3c0e7214be 100644 --- a/web/packages/teleport/src/services/user/makeAcl.ts +++ b/web/packages/teleport/src/services/user/makeAcl.ts @@ -30,6 +30,7 @@ export default function makeAcl(json): Acl { const tokens = json.tokens || defaultAccess; const accessRequests = json.accessRequests || defaultAccess; const billing = json.billing || defaultAccess; + const plugins = json.plugins || defaultAccess; const dbServers = json.dbServers || defaultAccess; const db = json.db || defaultAccess; const desktops = json.desktops || defaultAccess; @@ -66,6 +67,7 @@ export default function makeAcl(json): Acl { tokens, accessRequests, billing, + plugins, dbServers, db, desktops, diff --git a/web/packages/teleport/src/services/user/types.ts b/web/packages/teleport/src/services/user/types.ts index 3eff215710f33..de1ad44617290 100644 --- a/web/packages/teleport/src/services/user/types.ts +++ b/web/packages/teleport/src/services/user/types.ts @@ -70,6 +70,7 @@ export interface Acl { connectionDiagnostic: Access; license: Access; download: Access; + plugins: Access; } export interface User { diff --git a/web/packages/teleport/src/services/user/user.test.ts b/web/packages/teleport/src/services/user/user.test.ts index 8350d3a7e577d..6f2d606b2aa7d 100644 --- a/web/packages/teleport/src/services/user/user.test.ts +++ b/web/packages/teleport/src/services/user/user.test.ts @@ -71,6 +71,13 @@ test('undefined values in context response gives proper default values', async ( read: false, remove: false, }, + plugins: { + create: false, + edit: false, + list: false, + read: false, + remove: false, + }, roles: { list: false, read: false, diff --git a/web/packages/teleport/src/stores/storeUserContext.ts b/web/packages/teleport/src/stores/storeUserContext.ts index 247b9988afc16..0d5f259b8fe06 100644 --- a/web/packages/teleport/src/stores/storeUserContext.ts +++ b/web/packages/teleport/src/stores/storeUserContext.ts @@ -171,4 +171,8 @@ export default class StoreUserContext extends Store { hasDiscoverAccess() { return this.hasPrereqAccessToAddAgents() || this.hasAccessToQueryAgent(); } + + hasPluginsAccess() { + return this.state.acl.plugins.list || this.state.acl.plugins.create; + } } diff --git a/web/packages/teleport/src/teleportContext.tsx b/web/packages/teleport/src/teleportContext.tsx index 7f755be34663a..8e16635704a83 100644 --- a/web/packages/teleport/src/teleportContext.tsx +++ b/web/packages/teleport/src/teleportContext.tsx @@ -97,6 +97,7 @@ class TeleportContext implements types.Context { accessRequests: false, downloadCenter: false, discover: false, + plugins: false, }; } @@ -118,6 +119,7 @@ class TeleportContext implements types.Context { newAccessRequest: userContext.getAccessRequestAccess().create, downloadCenter: userContext.hasDownloadCenterListAccess(), discover: userContext.hasDiscoverAccess(), + plugins: userContext.hasPluginsAccess(), }; } } diff --git a/web/packages/teleport/src/types.ts b/web/packages/teleport/src/types.ts index 0791b1aa98816..3b4de2d870f0c 100644 --- a/web/packages/teleport/src/types.ts +++ b/web/packages/teleport/src/types.ts @@ -89,4 +89,5 @@ export interface FeatureFlags { newAccessRequest: boolean; downloadCenter: boolean; discover: boolean; + plugins: boolean; }