Skip to content

Commit

Permalink
Merge pull request #665 from openziti/frontend_permissions
Browse files Browse the repository at this point in the history
Frontend Permissions (#539)
  • Loading branch information
michaelquigley authored Jun 20, 2024
2 parents ccc9695 + 8683d14 commit 2a21a67
Show file tree
Hide file tree
Showing 18 changed files with 217 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## v0.4.32

FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539)

FEATURE: Resource count limits now include `share_frontends` to limit the number of frontends that are allowed to make connections to a share (https://github.com/openziti/zrok/issues/650)

FIX: use controller config spec v4 in the Docker instance
Expand Down
16 changes: 12 additions & 4 deletions cmd/zrok/adminCreateFrontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/rest_client_zrok/admin"
"github.com/openziti/zrok/rest_model_zrok"
"github.com/openziti/zrok/sdk/golang/sdk"
"github.com/openziti/zrok/tui"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand All @@ -15,7 +16,8 @@ func init() {
}

type adminCreateFrontendCommand struct {
cmd *cobra.Command
cmd *cobra.Command
closed bool
}

func newAdminCreateFrontendCommand() *adminCreateFrontendCommand {
Expand All @@ -25,6 +27,7 @@ func newAdminCreateFrontendCommand() *adminCreateFrontendCommand {
Args: cobra.ExactArgs(3),
}
command := &adminCreateFrontendCommand{cmd: cmd}
cmd.Flags().BoolVar(&command.closed, "closed", false, "Enabled closed permission mode")
cmd.Run = command.run
return command
}
Expand All @@ -44,11 +47,16 @@ func (cmd *adminCreateFrontendCommand) run(_ *cobra.Command, args []string) {
panic(err)
}

permissionMode := sdk.OpenPermissionMode
if cmd.closed {
permissionMode = sdk.ClosedPermissionMode
}
req := admin.NewCreateFrontendParams()
req.Body = &rest_model_zrok.CreateFrontendRequest{
ZID: zId,
PublicName: publicName,
URLTemplate: urlTemplate,
ZID: zId,
PublicName: publicName,
URLTemplate: urlTemplate,
PermissionMode: string(permissionMode),
}

resp, err := zrok.Admin.CreateFrontend(req, mustGetAdminAuth())
Expand Down
12 changes: 6 additions & 6 deletions controller/createFrontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package controller

import (
"errors"

"github.com/go-openapi/runtime/middleware"
"github.com/lib/pq"
"github.com/mattn/go-sqlite3"
Expand Down Expand Up @@ -57,11 +56,12 @@ func (h *createFrontendHandler) Handle(params admin.CreateFrontendParams, princi
}

fe := &store.Frontend{
Token: feToken,
ZId: params.Body.ZID,
PublicName: &params.Body.PublicName,
UrlTemplate: &params.Body.URLTemplate,
Reserved: true,
Token: feToken,
ZId: params.Body.ZID,
PublicName: &params.Body.PublicName,
UrlTemplate: &params.Body.URLTemplate,
Reserved: true,
PermissionMode: store.PermissionMode(params.Body.PermissionMode),
}
if _, err := str.CreateGlobalFrontend(fe, tx); err != nil {
perr := &pq.Error{}
Expand Down
11 changes: 11 additions & 0 deletions controller/share.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
logrus.Error(err)
return share.NewShareNotFound()
}
if sfe.PermissionMode == store.ClosedPermissionMode {
granted, err := str.IsFrontendGrantedToAccount(int(principal.ID), sfe.Id, trx)
if err != nil {
logrus.Error(err)
return share.NewShareInternalServerError()
}
if !granted {
logrus.Errorf("'%v' is not granted access to frontend '%v'", principal.Email, frontendSelection)
return share.NewShareNotFound()
}
}
if sfe != nil && sfe.UrlTemplate != nil {
frontendZIds = append(frontendZIds, sfe.ZId)
frontendTemplates = append(frontendTemplates, *sfe.UrlTemplate)
Expand Down
14 changes: 7 additions & 7 deletions controller/store/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,28 @@ type Frontend struct {
PublicName *string
UrlTemplate *string
Reserved bool
Deleted bool
PermissionMode PermissionMode
}

func (str *Store) CreateFrontend(envId int, f *Frontend, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into frontends (environment_id, private_share_id, token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5, $6, $7) returning id")
stmt, err := tx.Prepare("insert into frontends (environment_id, private_share_id, token, z_id, public_name, url_template, reserved, permission_mode) values ($1, $2, $3, $4, $5, $6, $7, $8) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing frontends insert statement")
}
var id int
if err := stmt.QueryRow(envId, f.PrivateShareId, f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil {
if err := stmt.QueryRow(envId, f.PrivateShareId, f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved, f.PermissionMode).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing frontends insert statement")
}
return id, nil
}

func (str *Store) CreateGlobalFrontend(f *Frontend, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into frontends (token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5) returning id")
stmt, err := tx.Prepare("insert into frontends (token, z_id, public_name, url_template, reserved, permission_mode) values ($1, $2, $3, $4, $5, $6) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing global frontends insert statement")
}
var id int
if err := stmt.QueryRow(f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil {
if err := stmt.QueryRow(f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved, f.PermissionMode).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing global frontends insert statement")
}
return id, nil
Expand Down Expand Up @@ -122,12 +122,12 @@ func (str *Store) FindFrontendsForPrivateShare(shrId int, tx *sqlx.Tx) ([]*Front
}

func (str *Store) UpdateFrontend(fe *Frontend, tx *sqlx.Tx) error {
sql := "update frontends set environment_id = $1, private_share_id = $2, token = $3, z_id = $4, public_name = $5, url_template = $6, reserved = $7, updated_at = current_timestamp where id = $8"
sql := "update frontends set environment_id = $1, private_share_id = $2, token = $3, z_id = $4, public_name = $5, url_template = $6, reserved = $7, permission_mode = $8, updated_at = current_timestamp where id = $9"
stmt, err := tx.Prepare(sql)
if err != nil {
return errors.Wrap(err, "error preparing frontends update statement")
}
_, err = stmt.Exec(fe.EnvironmentId, fe.PrivateShareId, fe.Token, fe.ZId, fe.PublicName, fe.UrlTemplate, fe.Reserved, fe.Id)
_, err = stmt.Exec(fe.EnvironmentId, fe.PrivateShareId, fe.Token, fe.ZId, fe.PublicName, fe.UrlTemplate, fe.Reserved, fe.PermissionMode, fe.Id)
if err != nil {
return errors.Wrap(err, "error executing frontends update statement")
}
Expand Down
18 changes: 18 additions & 0 deletions controller/store/frontendGrant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package store

import (
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)

func (str *Store) IsFrontendGrantedToAccount(acctId, frontendId int, trx *sqlx.Tx) (bool, error) {
stmt, err := trx.Prepare("select count(0) from frontend_grants where account_id = $1 AND frontend_id = $2")
if err != nil {
return false, errors.Wrap(err, "error preparing frontend_grants select statement")
}
var count int
if err := stmt.QueryRow(acctId, frontendId).Scan(&count); err != nil {
return false, errors.Wrap(err, "error querying frontend_grants count")
}
return count > 0, nil
}
1 change: 0 additions & 1 deletion controller/store/share.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ type Share struct {
Reserved bool
UniqueName bool
PermissionMode PermissionMode
Deleted bool
}

func (str *Store) CreateShare(envId int, shr *Share, tx *sqlx.Tx) (int, error) {
Expand Down
17 changes: 17 additions & 0 deletions controller/store/sql/postgresql/027_v0_4_32_frontend_grants.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- +migrate Up

alter table frontends add column permission_mode permission_mode_type not null default('open');

create table frontend_grants (
id serial primary key,

account_id integer references accounts (id) not null,
frontend_id integer references frontends (id) not null,

created_at timestamptz not null default(current_timestamp),
updated_at timestamptz not null default(current_timestamp),
deleted boolean not null default(false)
);

create index frontend_grants_account_id_idx on frontend_grants (account_id);
create index frontend_grants_frontend_id_idx on frontend_grants (frontend_id);
17 changes: 17 additions & 0 deletions controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- +migrate Up

alter table frontends add column permission_mode string not null default('open');

create table frontend_grants (
id integer primary key,

account_id integer references accounts (id) not null,
frontend_id integer references frontends (id) not null,

created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
deleted boolean not null default(false)
);

create index frontend_grants_account_id_idx on frontend_grants (account_id);
create index frontend_grants_frontend_id_idx on frontend_grants (frontend_id);
58 changes: 58 additions & 0 deletions rest_model_zrok/create_frontend_request.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions rest_server_zrok/embedded_spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/nodejs/sdk/src/zrok/api/.openapi-generator/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.4.0
7.6.0
12 changes: 12 additions & 0 deletions sdk/nodejs/sdk/src/zrok/api/model/createFrontendRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class CreateFrontendRequest {
'zId'?: string;
'urlTemplate'?: string;
'publicName'?: string;
'permissionMode'?: CreateFrontendRequest.PermissionModeEnum;

static discriminator: string | undefined = undefined;

Expand All @@ -34,10 +35,21 @@ export class CreateFrontendRequest {
"name": "publicName",
"baseName": "public_name",
"type": "string"
},
{
"name": "permissionMode",
"baseName": "permissionMode",
"type": "CreateFrontendRequest.PermissionModeEnum"
} ];

static getAttributeTypeMap() {
return CreateFrontendRequest.attributeTypeMap;
}
}

export namespace CreateFrontendRequest {
export enum PermissionModeEnum {
Open = <any> 'open',
Closed = <any> 'closed'
}
}
1 change: 1 addition & 0 deletions sdk/nodejs/sdk/src/zrok/api/model/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ let primitives = [
];

let enumsMap: {[index: string]: any} = {
"CreateFrontendRequest.PermissionModeEnum": CreateFrontendRequest.PermissionModeEnum,
"ShareRequest.ShareModeEnum": ShareRequest.ShareModeEnum,
"ShareRequest.BackendModeEnum": ShareRequest.BackendModeEnum,
"ShareRequest.OauthProviderEnum": ShareRequest.OauthProviderEnum,
Expand Down
3 changes: 2 additions & 1 deletion sdk/nodejs/sdk/src/zrok/api/model/shareRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ export namespace ShareRequest {
UdpTunnel = <any> 'udpTunnel',
Caddy = <any> 'caddy',
Drive = <any> 'drive',
Socks = <any> 'socks'
Socks = <any> 'socks',
Vpn = <any> 'vpn'
}
export enum OauthProviderEnum {
Github = <any> 'github',
Expand Down
Loading

0 comments on commit 2a21a67

Please sign in to comment.