diff --git a/.changelog/11409.txt b/.changelog/11409.txt new file mode 100644 index 000000000000..21fe036d509e --- /dev/null +++ b/.changelog/11409.txt @@ -0,0 +1,4 @@ +```release-note:bug +ui: Ensure we check intention permissions for specific services when deciding +whether to show action buttons for per service intention actions +``` diff --git a/ui/packages/consul-ui/app/abilities/service-instance.js b/ui/packages/consul-ui/app/abilities/service-instance.js index 628a92c75e19..07a11a41a330 100644 --- a/ui/packages/consul-ui/app/abilities/service-instance.js +++ b/ui/packages/consul-ui/app/abilities/service-instance.js @@ -1,5 +1,14 @@ -import BaseAbility from './base'; +import BaseAbility, { ACCESS_READ, ACCESS_WRITE } from './base'; export default class ServiceInstanceAbility extends BaseAbility { resource = 'service'; + generateForSegment(segment) { + // When we ask for service-instances its almost like a request for a single service + // When we do that we also want to know if we can read/write intentions for services + // so here we add intentions read/write for the specific segment/service prefix + return super.generateForSegment(...arguments).concat([ + this.permissions.generate('intention', ACCESS_READ, segment), + this.permissions.generate('intention', ACCESS_WRITE, segment), + ]); + } } diff --git a/ui/packages/consul-ui/app/abilities/service.js b/ui/packages/consul-ui/app/abilities/service.js index 5e806bd6f76d..8244f0b3f836 100644 --- a/ui/packages/consul-ui/app/abilities/service.js +++ b/ui/packages/consul-ui/app/abilities/service.js @@ -6,4 +6,32 @@ export default class ServiceAbility extends BaseAbility { get isLinkable() { return this.item.InstanceCount > 0; } + + get canReadIntention() { + if (typeof this.item === 'undefined' || typeof this.item.Resources === 'undefined') { + return false; + } + const found = this.item.Resources.find( + item => item.Resource === 'intention' && item.Access === 'read' && item.Allow === true + ); + return typeof found !== 'undefined'; + } + + get canWriteIntention() { + if (typeof this.item === 'undefined' || typeof this.item.Resources === 'undefined') { + return false; + } + const found = this.item.Resources.find( + item => item.Resource === 'intention' && item.Access === 'write' && item.Allow === true + ); + return typeof found !== 'undefined'; + } + + get canCreateIntention() { + return this.canWriteIntention; + } + + get canUpdateIntention() { + return this.canWriteIntention; + } } diff --git a/ui/packages/consul-ui/app/components/consul/intention/form/index.hbs b/ui/packages/consul-ui/app/components/consul/intention/form/index.hbs index 1806e28c79c3..98dc57c23907 100644 --- a/ui/packages/consul-ui/app/components/consul/intention/form/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/intention/form/index.hbs @@ -33,7 +33,7 @@ as |api|> {{#let api.data as |item|}} -{{#if (can 'write intention' item=item)}} +{{#if (not readonly)}} {{#let (changeset-get item 'Action') as |newAction|}} {{/if}} +{{#let (not (can 'update intention for service' item=@service.Service)) as |disabled|}} {{#each @items as |item|}} {{#if (or (not item.Intention.Allowed) item.Intention.HasPermissions)}} {{else if (and item.Intention.Allowed (not item.TransparentProxy) (eq item.Source 'specific-intention'))}} @@ -96,8 +98,10 @@ @service={{@service}} @position={{find-by 'id' (concat this.guid item.Namespace item.Name) this.iconPositions}} @item={{item}} + @disabled={{disabled}} @oncreate={{action @oncreate item @service}} /> {{/if}} {{/each}} +{{/let}} diff --git a/ui/packages/consul-ui/app/components/topology-metrics/popover/index.hbs b/ui/packages/consul-ui/app/components/topology-metrics/popover/index.hbs index ccd29d8eb2be..96f237cba482 100644 --- a/ui/packages/consul-ui/app/components/topology-metrics/popover/index.hbs +++ b/ui/packages/consul-ui/app/components/topology-metrics/popover/index.hbs @@ -2,133 +2,147 @@ class="topology-metrics-popover {{@type}}" ...attributes > - -{{#if (eq @type 'deny')}} - - <:header> -

- {{t "components.consul.topology-metrics.popover.deny.header"}} -

- - <:body> -

- {{#if @item.Intention.HasExact}} - {{t "components.consul.topology-metrics.popover.deny.body.isExact"}} - {{else}} - {{t "components.consul.topology-metrics.popover.deny.body.notExact"}} - {{/if}} -

- - <:actions as |Actions|> - - - - - - - -
-{{else if (eq @type 'not-defined')}} - - <:header> -

- {{t "components.consul.topology-metrics.popover.not-defined.header"}} -

- - <:body> -

- {{t "components.consul.topology-metrics.popover.not-defined.body" downstream=@item.Name upstream=@service.Name }} -

- - <:actions as |Actions|> - - - {{t "components.consul.topology-metrics.popover.not-defined.action"}} - - - - - - -
-{{else}} - - <:header> -

- {{t "components.consul.topology-metrics.popover.l7.header"}} -

- - <:body> -

- {{t "components.consul.topology-metrics.popover.l7.body"}} -

- - <:actions as |Actions|> - - - {{t "components.consul.topology-metrics.popover.l7.action"}} - - - - - - -
-{{/if}} - +{{#let + (concat 'top:' @position.y 'px;left:' @position.x 'px;') + (if (eq @type 'deny') 'Add intention' 'View intention') +as |style label|}} + {{#if (not @disabled)}} + {{#if (eq @type 'deny')}} + + <:header> +

+ {{t "components.consul.topology-metrics.popover.deny.header"}} +

+ + <:body> +

+ {{#if @item.Intention.HasExact}} + {{t "components.consul.topology-metrics.popover.deny.body.isExact"}} + {{else}} + {{t "components.consul.topology-metrics.popover.deny.body.notExact"}} + {{/if}} +

+ + <:actions as |Actions|> + + + + + + + +
+ {{else if (eq @type 'not-defined')}} + + <:header> +

+ {{t "components.consul.topology-metrics.popover.not-defined.header"}} +

+ + <:body> +

+ {{t "components.consul.topology-metrics.popover.not-defined.body" downstream=@item.Name upstream=@service.Name }} +

+ + <:actions as |Actions|> + + + {{t "components.consul.topology-metrics.popover.not-defined.action"}} + + + + + + +
+ {{else}} + + <:header> +

+ {{t "components.consul.topology-metrics.popover.l7.header"}} +

+ + <:body> +

+ {{t "components.consul.topology-metrics.popover.l7.body"}} +

+ + <:actions as |Actions|> + + + {{t "components.consul.topology-metrics.popover.l7.action"}} + + + + + + +
+ {{/if}} + {{! TODO: Once we can conditionally add a modifier these two buttons }} + {{! should be replaced with a conditional modifer instead }} + + {{else}} + + {{/if}} +{{/let}} - - diff --git a/ui/packages/consul-ui/app/components/topology-metrics/up-lines/index.hbs b/ui/packages/consul-ui/app/components/topology-metrics/up-lines/index.hbs index 7f73d5c5654c..33b9e59e483e 100644 --- a/ui/packages/consul-ui/app/components/topology-metrics/up-lines/index.hbs +++ b/ui/packages/consul-ui/app/components/topology-metrics/up-lines/index.hbs @@ -82,13 +82,13 @@ {{/each}} {{/if}} - {{#each @items as |item|}} {{#if (or (not item.Intention.Allowed) item.Intention.HasPermissions)}} {{/if}} diff --git a/ui/packages/consul-ui/app/services/repository.js b/ui/packages/consul-ui/app/services/repository.js index 78920b2b8262..cf7ecd12ea75 100644 --- a/ui/packages/consul-ui/app/services/repository.js +++ b/ui/packages/consul-ui/app/services/repository.js @@ -79,9 +79,12 @@ export default class RepositoryService extends Service { throw e; } } - const item = await cb(); + const item = await cb(params.resources); // add the `Resource` information to the record/model so we can inspect // them in other places like templates etc + // TODO: We mostly use this to authorize single items but we do + // occasionally get an array back here e.g. service-instances, so we + // should make this fact more obvious if (get(item, 'Resources')) { set(item, 'Resources', params.resources); } diff --git a/ui/packages/consul-ui/app/services/repository/service-instance.js b/ui/packages/consul-ui/app/services/repository/service-instance.js index 25e65f762190..d69fc067def0 100644 --- a/ui/packages/consul-ui/app/services/repository/service-instance.js +++ b/ui/packages/consul-ui/app/services/repository/service-instance.js @@ -21,12 +21,15 @@ export default class ServiceInstanceService extends RepositoryService { params.index = configuration.cursor; params.uri = configuration.uri; } - const instances = await this.authorizeBySlug( - async () => this.query(params), + return this.authorizeBySlug( + async (resources) => { + const instances = await this.query(params); + set(instances, 'firstObject.Service.Resources', resources); + return instances; + }, ACCESS_READ, params ); - return instances; } @dataSource('/:partition/:ns/:dc/service-instance/:serviceId/:node/:id') diff --git a/ui/packages/consul-ui/app/templates/dc/intentions/edit.hbs b/ui/packages/consul-ui/app/templates/dc/intentions/edit.hbs index bc931c5ea38f..2139fd73186f 100644 --- a/ui/packages/consul-ui/app/templates/dc/intentions/edit.hbs +++ b/ui/packages/consul-ui/app/templates/dc/intentions/edit.hbs @@ -22,7 +22,8 @@ as |route|> {{#let loader.data -as |item|}} + (not (can "write intention" item=item)) +as |item readOnly|}}
    @@ -31,7 +32,7 @@ as |item|}}

    - {{#if (can "write intention" item=item)}} + {{#if (not readOnly)}} {{#if item.ID}} {{else}} @@ -44,6 +45,7 @@ as |item|}} diff --git a/ui/packages/consul-ui/app/templates/dc/services/show/intentions/edit.hbs b/ui/packages/consul-ui/app/templates/dc/services/show/intentions/edit.hbs index f3980e804021..44bbc9d03703 100644 --- a/ui/packages/consul-ui/app/templates/dc/services/show/intentions/edit.hbs +++ b/ui/packages/consul-ui/app/templates/dc/services/show/intentions/edit.hbs @@ -1,14 +1,17 @@ +{{#let (not (can "write intention for service" item=item.Service)) as |readOnly|}} +{{/let}} diff --git a/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs b/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs index a2a4b26250f3..87d2f96492dd 100644 --- a/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs @@ -38,10 +38,11 @@ as |route|> ) api.data + route.model.item - as |sort filters items|}} + as |sort filters items item|}}
    - {{#if (can 'create intentions')}} + {{#if (can 'create intention for service' item=item.Service)}} Create